No OneTemporary

File Metadata

Created
Sat, May 11, 9:23 PM
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/kmymoney/converter/existingtransactionmatchfinder.cpp b/kmymoney/converter/existingtransactionmatchfinder.cpp
index 7bedd28b1..e5d7f2b3c 100644
--- a/kmymoney/converter/existingtransactionmatchfinder.cpp
+++ b/kmymoney/converter/existingtransactionmatchfinder.cpp
@@ -1,55 +1,56 @@
/***************************************************************************
KMyMoney transaction importing module - searches for a matching transaction in the ledger
copyright : (C) 2012 by Lukasz Maszczynski <lukasz@maszczynski.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 "existingtransactionmatchfinder.h"
#include <QDebug>
#include "mymoneyfile.h"
+#include "mymoneytransactionfilter.h"
ExistingTransactionMatchFinder::ExistingTransactionMatchFinder(int matchWindow)
: TransactionMatchFinder(matchWindow)
{
}
void ExistingTransactionMatchFinder::createListOfMatchCandidates()
{
MyMoneyTransactionFilter filter(importedSplit.accountId());
filter.setReportAllSplits(false);
filter.setDateFilter(importedTransaction.postDate().addDays(-matchWindow), importedTransaction.postDate().addDays(matchWindow));
filter.setAmountFilter(importedSplit.shares(), importedSplit.shares());
MyMoneyFile::instance()->transactionList(listOfMatchCandidates, filter);
qDebug() << "Considering" << listOfMatchCandidates.size() << "existing transaction(s) for matching";
}
void ExistingTransactionMatchFinder::findMatchInMatchCandidatesList()
{
foreach (const TransactionAndSplitPair & transactionAndSplit, listOfMatchCandidates) {
const MyMoneyTransaction & theTransaction = transactionAndSplit.first;
if (theTransaction.id() == importedTransaction.id()) {
// just skip myself
continue;
}
findMatchingSplit(theTransaction, 0);
if (matchResult != MatchNotFound) {
return;
}
}
}
diff --git a/kmymoney/converter/mymoneygncreader.cpp b/kmymoney/converter/mymoneygncreader.cpp
index 1faf22043..390410c3f 100644
--- a/kmymoney/converter/mymoneygncreader.cpp
+++ b/kmymoney/converter/mymoneygncreader.cpp
@@ -1,2644 +1,2646 @@
/***************************************************************************
mymoneygncreader - description
-------------------
begin : Wed Mar 3 2004
copyright : (C) 2000-2004 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "mymoneygncreader.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QFile>
#include <QMap>
#include <QIcon>
#include <QInputDialog>
#include <QFileDialog>
#include <QTextCodec>
#include <QTextStream>
#include <QDebug>
#include <QXmlAttributes>
#include <QXmlInputSource>
#include <QXmlSimpleReader>
// ----------------------------------------------------------------------------
// KDE Includes
#ifndef _GNCFILEANON
#include <KConfig>
#include <KMessageBox>
#endif
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Third party Includes
// ------------------------------------------------------------Box21----------------
// Project Includes
#include "config-kmymoney.h"
#include "imymoneyserialize.h"
#ifndef _GNCFILEANON
#include "storage/imymoneystorage.h"
#include "kmymoneyutils.h"
#include "mymoneyfile.h"
+#include "mymoneyschedule.h"
#include "mymoneyprice.h"
+#include "mymoneyexception.h"
#include "kgncimportoptionsdlg.h"
#include "kgncpricesourcedlg.h"
#include "keditscheduledlg.h"
#include "kmymoneyedit.h"
#define TRY try
#define CATCH catch (const MyMoneyException &)
#define PASS catch (const MyMoneyException &) { throw; }
#else
#include "mymoneymoney.h"
#include <KTextEdit>
// #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 <?xml header.
This SEEMS to have been rectified in Qt4 though I have little test data
to prove this conclusively.
If true, we can remove the encoding option in the import options dialog
and this code too.*/
return (pMain->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<GncKvp>::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<QString, QString> anonPayees; // to check for duplicate payee names
static QMap<QString, QString> anonStocks; // for reference to equities
QString result(data);
QMap<QString, QString>::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 <GncKvp*>(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<GncCmdtySpec *>(subObj);
break;
case CURR:
m_vpCurrency = static_cast<GncCmdtySpec *>(subObj);
break;
case PRICEDATE:
m_vpPriceDate = static_cast<GncDate *>(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<GncCmdtySpec *>(subObj);
break;
case KVP:
m_kvpList.append(*(static_cast <GncKvp*>(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<GncCmdtySpec *>(subObj);
break;
case POSTED:
m_vpDatePosted = static_cast<GncDate *>(subObj);
break;
case ENTERED:
m_vpDateEntered = static_cast<GncDate *>(subObj);
break;
case SPLIT:
m_splitList.append(subObj);
break;
case KVP:
m_kvpList.append(*(static_cast <GncKvp*>(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<GncDate *>(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 <GncKvp*>(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<GncDate *>(subObj);
break;
case LASTDATE:
m_vpLastDate = static_cast<GncDate *>(subObj);
break;
case ENDDATE:
m_vpEndDate = static_cast<GncDate *>(subObj);
break;
case FREQ:
m_vpFreqSpec = static_cast<GncFreqSpec *>(subObj);
break;
case RECURRENCE:
m_vpRecurrence.append(static_cast<GncRecurrence *>(subObj));
break;
case DEFINST:
m_vpSchedDef = static_cast<GncSchedDef *>(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<GncDate *>(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 << "<?xml version=\"1.0\"?>";
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 << "</" << elName << '>' ;
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('<', "&lt;");
anonData.replace('>', "&gt;");
anonData.replace('&', "&amp;");
pMain->oStream << anonData; // write original data
lastType = 1;
#endif // _GNCFILEANON
}
return (true);
}
bool XmlReader::endDocument()
{
#ifdef _GNCFILEANON
pMain->oStream << endl << endl;
pMain->oStream << "<!-- Local variables: -->" << endl;
pMain->oStream << "<!-- mode: xml -->" << endl;
pMain->oStream << "<!-- End: -->" << 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)
{
Q_CHECK_PTR(pDevice);
Q_CHECK_PTR(storage);
m_storage = dynamic_cast<IMyMoneyStorage *>(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;
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 <QApplication>
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::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 <jsled@asynchronous.org>
"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(MyMoneyAccount::Checkings);
+ acc.setAccountType(Account::Checkings);
} else if ("SAVINGS" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::Savings);
+ acc.setAccountType(Account::Savings);
} else if ("ASSET" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::Asset);
+ acc.setAccountType(Account::Asset);
} else if ("CASH" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::Cash);
+ acc.setAccountType(Account::Cash);
} else if ("CURRENCY" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::Cash);
+ acc.setAccountType(Account::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(MyMoneyAccount::Investment);
+ acc.setAccountType(Account::Investment);
} else {
- acc.setAccountType(MyMoneyAccount::Stock);
+ acc.setAccountType(Account::Stock);
}
} else if ("EQUITY" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::Equity);
+ acc.setAccountType(Account::Equity);
} else if ("LIABILITY" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::Liability);
+ acc.setAccountType(Account::Liability);
} else if ("CREDIT" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::CreditCard);
+ acc.setAccountType(Account::CreditCard);
} else if ("INCOME" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::Income);
+ acc.setAccountType(Account::Income);
} else if ("EXPENSE" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::Expense);
+ acc.setAccountType(Account::Expense);
} else if ("RECEIVABLE" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::Asset);
+ acc.setAccountType(Account::Asset);
} else if ("PAYABLE" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::Liability);
+ acc.setAccountType(Account::Liability);
} else if ("MONEYMRKT" == gac->type()) {
- acc.setAccountType(MyMoneyAccount::MoneyMarket);
+ acc.setAccountType(Account::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 MyMoneyAccount::Asset:
+ case Account::Asset:
acc.setParentAccountId(m_storage->asset().id());
break;
- case MyMoneyAccount::Liability:
+ case Account::Liability:
acc.setParentAccountId(m_storage->liability().id());
break;
- case MyMoneyAccount::Income:
+ case Account::Income:
acc.setParentAccountId(m_storage->income().id());
break;
- case MyMoneyAccount::Expense:
+ case Account::Expense:
acc.setParentAccountId(m_storage->expense().id());
break;
- case MyMoneyAccount::Equity:
+ case Account::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() == MyMoneyAccount::Stock) {
+ if (acc.accountType() == Account::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::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" << KMyMoneyUtils::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<const GncSplit *>(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<const GncSplit *>(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<MyMoneySplit>::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::ActionTransfer);
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(MyMoneySplit::NotReconciled);
break;
case 'c':
split.setReconcileFlag(MyMoneySplit::Cleared);
break;
case 'y':
split.setReconcileFlag(MyMoneySplit::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 MyMoneyAccount::Asset:
- if (splitAccount.accountType() == MyMoneyAccount::Stock) {
+ case Account::Asset:
+ if (splitAccount.accountType() == Account::Stock) {
split.value().isZero() ?
split.setAction(MyMoneySplit::ActionAddShares) : // free shares?
split.setAction(MyMoneySplit::ActionBuyShares);
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::ActionAddShares);
}
}
} 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::ActionCheck);
} else {
split.setAction(MyMoneySplit::ActionWithdrawal);
}
} else {
split.setAction(MyMoneySplit::ActionDeposit);
}
}
m_splitList.append(split);
break;
- case MyMoneyAccount::Liability:
+ case Account::Liability:
split.value().isNegative() ?
split.setAction(MyMoneySplit::ActionWithdrawal) :
split.setAction(MyMoneySplit::ActionDeposit);
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<const GncTemplateSplit *>(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<MyMoneySplit>::iterator it = m_splitList.begin();
while (!m_splitList.isEmpty()) {
split = *it;
if (m_potentialTransfer) {
split.setAction(MyMoneySplit::ActionTransfer);
} else {
if (split.value().isNegative()) {
//split.setAction (negativeActionType);
split.setAction(MyMoneySplit::ActionWithdrawal);
} else {
//split.setAction (positiveActionType);
split.setAction(MyMoneySplit::ActionDeposit);
}
}
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(MyMoneySplit::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(MyMoneyMoney::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 MyMoneyAccount::Asset:
+ case Account::Asset:
m_splitList.append(split);
break;
- case MyMoneyAccount::Liability:
+ case Account::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<GncTransaction*>::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<const GncTemplateSplit *>((*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;
- MyMoneySchedule::occurrenceE occ; // equivalent occurrence code
- MyMoneySchedule::weekendOptionE wo;
+ 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, MyMoneySchedule::OCCUR_ONCE, MyMoneySchedule::MoveNothing },
- {"daily" , 'd', 1, MyMoneySchedule::OCCUR_DAILY, MyMoneySchedule::MoveNothing },
- //{"daily_mf", 'd', 1, MyMoneySchedule::OCCUR_DAILY, MyMoneySchedule::MoveAfter }, doesn't work, need new freq in kmm
- {"30-days" , 'd', 30, MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS, MyMoneySchedule::MoveNothing },
- {"weekly", 'w', 1, MyMoneySchedule::OCCUR_WEEKLY, MyMoneySchedule::MoveNothing },
- {"bi_weekly", 'w', 2, MyMoneySchedule::OCCUR_EVERYOTHERWEEK, MyMoneySchedule::MoveNothing },
- {"three-weekly", 'w', 3, MyMoneySchedule::OCCUR_EVERYTHREEWEEKS, MyMoneySchedule::MoveNothing },
- {"four-weekly", 'w', 4, MyMoneySchedule::OCCUR_EVERYFOURWEEKS,
- MyMoneySchedule::MoveNothing },
- {"eight-weekly", 'w', 8, MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS, MyMoneySchedule::MoveNothing },
- {"monthly", 'm', 1, MyMoneySchedule::OCCUR_MONTHLY, MyMoneySchedule::MoveNothing },
- {"two-monthly", 'm', 2, MyMoneySchedule::OCCUR_EVERYOTHERMONTH,
- MyMoneySchedule::MoveNothing },
- {"quarterly", 'm', 3, MyMoneySchedule::OCCUR_QUARTERLY, MyMoneySchedule::MoveNothing },
- {"tri_annually", 'm', 4, MyMoneySchedule::OCCUR_EVERYFOURMONTHS, MyMoneySchedule::MoveNothing },
- {"semi_yearly", 'm', 6, MyMoneySchedule::OCCUR_TWICEYEARLY, MyMoneySchedule::MoveNothing },
- {"yearly", 'y', 1, MyMoneySchedule::OCCUR_YEARLY, MyMoneySchedule::MoveNothing },
- {"two-yearly", 'y', 2, MyMoneySchedule::OCCUR_EVERYOTHERYEAR,
- MyMoneySchedule::MoveNothing },
- {"zzz", 'y', 1, MyMoneySchedule::OCCUR_YEARLY, MyMoneySchedule::MoveNothing}
+ {"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(MyMoneySchedule::OCCUR_ONCE); // FIXME - how to convert
+ 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((MyMoneySchedule::paymentTypeE)MyMoneySchedule::STYPE_OTHER);
+ 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::ActionDeposit) {
- sc.setType((MyMoneySchedule::typeE)MyMoneySchedule::TYPE_DEPOSIT);
+ sc.setType((Schedule::Type)Schedule::Type::Deposit);
} else if (actionType == MyMoneySplit::ActionTransfer) {
- sc.setType((MyMoneySchedule::typeE)MyMoneySchedule::TYPE_TRANSFER);
+ sc.setType((Schedule::Type)Schedule::Type::Transfer);
} else {
- sc.setType((MyMoneySchedule::typeE)MyMoneySchedule::TYPE_BILL);
+ 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<QString>::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<MyMoneyAccount> list;
QList<MyMoneyAccount>::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<QString, unsigned int>::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<QString> 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) {
MyMoneySchedule sc = m_storage->schedule(m_suspectList[si]);
KEditScheduleDlg *s;
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:
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<const QString>(s));
}
PASS
}
//************************ writeReportToFile*********************************
bool MyMoneyGncReader::writeReportToFile(const QList<QString>& 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(MyMoneyAccount::Asset);
+ acc.setAccountType(Account::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() == MyMoneyAccount::Investment) &&
- (parent.accountType() != MyMoneyAccount::Asset)) {
+ if ((child.accountType() == Account::Investment) &&
+ (parent.accountType() != Account::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() == MyMoneyAccount::Income) &&
- (parent.accountType() != MyMoneyAccount::Income)) {
+ if ((child.accountType() == Account::Income) &&
+ (parent.accountType() != Account::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() == MyMoneyAccount::Expense) &&
- (parent.accountType() != MyMoneyAccount::Expense)) {
+ if ((child.accountType() == Account::Expense) &&
+ (parent.accountType() != Account::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() == MyMoneyAccount::Investment) return ;
+ if (parent.accountType() == Account::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(MyMoneyAccount::Investment);
+ invAcc.setAccountType(Account::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(MyMoneyAccount::Investment);
+ singleInvAcc.setAccountType(Account::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<MyMoneyAccount> list;
QList<MyMoneyAccount>::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() == MyMoneyAccount::Asset) && ((*acc).accountType() != MyMoneyAccount::Stock)) accList.append ((*acc).name());
- if ((*acc).accountType() == MyMoneyAccount::Investment) accList.append((*acc).name());
+ // if (((*acc).accountGroup() == Account::Asset) && ((*acc).accountType() != Account::Stock)) accList.append ((*acc).name());
+ if ((*acc).accountType() == Account::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(MyMoneyAccount::Investment);
+ invAcc.setAccountType(Account::Investment);
invAcc.setName(invAccName);
invAcc.setCurrencyId(QString(""));
invAcc.setParentAccountId(m_storage->asset().id());
m_storage->addAccount(invAcc);
ok = true;
}
- if (invAcc.accountType() == MyMoneyAccount::Investment) {
+ if (invAcc.accountType() == Account::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<QString, QString>::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<KGncPriceSourceDlg> 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/converter/mymoneystatementreader.cpp b/kmymoney/converter/mymoneystatementreader.cpp
index fe8f2a48e..82888c0d5 100644
--- a/kmymoney/converter/mymoneystatementreader.cpp
+++ b/kmymoney/converter/mymoneystatementreader.cpp
@@ -1,1535 +1,1538 @@
/***************************************************************************
mymoneystatementreader.cpp
-------------------
begin : Mon Aug 30 2004
copyright : (C) 2000-2004 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@users.sourceforge.net>
Ace Jones <acejones@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 "mymoneystatementreader.h"
#include <typeinfo>
// ----------------------------------------------------------------------------
// QT Headers
#include <QStringList>
#include <QLabel>
#include <QList>
#include <QVBoxLayout>
#include <QDialog>
#include <QDialogButtonBox>
// ----------------------------------------------------------------------------
// KDE Headers
#include <KMessageBox>
#include <KConfig>
#include <KSharedConfig>
#include <KConfigGroup>
#include <KGuiItem>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Headers
#include "mymoneyfile.h"
+#include "mymoneyprice.h"
+#include "mymoneytransactionfilter.h"
+#include "mymoneypayee.h"
#include "mymoneystatement.h"
#include "kmymoneyglobalsettings.h"
#include "transactioneditor.h"
#include "kmymoneyedit.h"
#include "kaccountselectdlg.h"
#include "transactionmatcher.h"
#include "kenterscheduledlg.h"
#include "kmymoney.h"
#include "kmymoneyaccountcombo.h"
#include "accountsmodel.h"
#include "models.h"
#include "existingtransactionmatchfinder.h"
#include "scheduledtransactionmatchfinder.h"
using namespace eMyMoney;
bool matchNotEmpty(const QString &l, const QString &r)
{
return !l.isEmpty() && QString::compare(l, r, Qt::CaseInsensitive) == 0;
}
class MyMoneyStatementReader::Private
{
public:
Private() :
transactionsCount(0),
transactionsAdded(0),
transactionsMatched(0),
transactionsDuplicate(0),
scannedCategories(false) {}
const QString& feeId(const MyMoneyAccount& invAcc);
const QString& interestId(const MyMoneyAccount& invAcc);
QString interestId(const QString& name);
QString expenseId(const QString& name);
QString feeId(const QString& name);
void assignUniqueBankID(MyMoneySplit& s, const MyMoneyStatement::Transaction& t_in);
void setupPrice(MyMoneySplit &s, const MyMoneyAccount &splitAccount, const MyMoneyAccount &transactionAccount, const QDate &postDate);
MyMoneyAccount lastAccount;
QList<MyMoneyTransaction> transactions;
QList<MyMoneyPayee> payees;
int transactionsCount;
int transactionsAdded;
int transactionsMatched;
int transactionsDuplicate;
QMap<QString, bool> uniqIds;
QMap<QString, MyMoneySecurity> securitiesBySymbol;
QMap<QString, MyMoneySecurity> securitiesByName;
bool m_skipCategoryMatching;
private:
void scanCategories(QString& id, const MyMoneyAccount& invAcc, const MyMoneyAccount& parentAccount, const QString& defaultName);
/**
* This method tries to figure out the category to be used for fees and interest
* from previous transactions in the given @a investmentAccount and returns the
* ids of those categories in @a feesId and @a interestId. The last used category
* will be returned.
*/
void previouslyUsedCategories(const QString& investmentAccount, QString& feesId, QString& interestId);
QString nameToId(const QString&name, MyMoneyAccount& parent);
private:
QString m_feeId;
QString m_interestId;
bool scannedCategories;
};
const QString& MyMoneyStatementReader::Private::feeId(const MyMoneyAccount& invAcc)
{
scanCategories(m_feeId, invAcc, MyMoneyFile::instance()->expense(), i18n("_Fees"));
return m_feeId;
}
const QString& MyMoneyStatementReader::Private::interestId(const MyMoneyAccount& invAcc)
{
scanCategories(m_interestId, invAcc, MyMoneyFile::instance()->income(), i18n("_Dividend"));
return m_interestId;
}
QString MyMoneyStatementReader::Private::nameToId(const QString& name, MyMoneyAccount& parent)
{
// Adapted from KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal)
// Needed to find/create category:sub-categories
MyMoneyFile* file = MyMoneyFile::instance();
- QString id = file->categoryToAccount(name, MyMoneyAccount::UnknownAccountType);
+ QString id = file->categoryToAccount(name, Account::Unknown);
// if it does not exist, we have to create it
if (id.isEmpty()) {
MyMoneyAccount newAccount;
MyMoneyAccount parentAccount = parent;
newAccount.setName(name) ;
int pos;
// check for ':' in the name and use it as separator for a hierarchy
while ((pos = newAccount.name().indexOf(MyMoneyFile::AccountSeperator)) != -1) {
QString part = newAccount.name().left(pos);
QString remainder = newAccount.name().mid(pos + 1);
const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, part);
if (existingAccount.id().isEmpty()) {
newAccount.setName(part);
newAccount.setAccountType(parentAccount.accountType());
file->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);
}//end while
newAccount.setAccountType(parentAccount.accountType());
// make sure we have a currency. If none is assigned, we assume base currency
if (newAccount.currencyId().isEmpty())
newAccount.setCurrencyId(file->baseCurrency().id());
file->addAccount(newAccount, parentAccount);
id = newAccount.id();
}
return id;
}
QString MyMoneyStatementReader::Private::expenseId(const QString& name)
{
MyMoneyAccount parent = MyMoneyFile::instance()->expense();
return nameToId(name, parent);
}
QString MyMoneyStatementReader::Private::interestId(const QString& name)
{
MyMoneyAccount parent = MyMoneyFile::instance()->income();
return nameToId(name, parent);
}
QString MyMoneyStatementReader::Private::feeId(const QString& name)
{
MyMoneyAccount parent = MyMoneyFile::instance()->expense();
return nameToId(name, parent);
}
void MyMoneyStatementReader::Private::previouslyUsedCategories(const QString& investmentAccount, QString& feesId, QString& interestId)
{
feesId.clear();
interestId.clear();
MyMoneyFile* file = MyMoneyFile::instance();
try {
MyMoneyAccount acc = file->account(investmentAccount);
MyMoneyTransactionFilter filter(investmentAccount);
filter.setReportAllSplits(false);
// since we assume an investment account here, we need to collect the stock accounts as well
filter.addAccount(acc.accountList());
QList< QPair<MyMoneyTransaction, MyMoneySplit> > list;
file->transactionList(list, filter);
QList< QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it_t;
for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) {
const MyMoneyTransaction& t = (*it_t).first;
MyMoneySplit s = (*it_t).second;
MyMoneyAccount acc = file->account(s.accountId());
// stock split shouldn't be fee or interest bacause it won't play nice with dissectTransaction
// it was caused by processTransactionEntry adding splits in wrong order != with manual transaction entering
- if (acc.accountGroup() == MyMoneyAccount::Expense || acc.accountGroup() == MyMoneyAccount::Income) {
+ if (acc.accountGroup() == Account::Expense || acc.accountGroup() == Account::Income) {
foreach (auto sNew , t.splits()) {
acc = file->account(sNew.accountId());
- if (acc.accountGroup() != MyMoneyAccount::Expense && // shouldn't be fee
- acc.accountGroup() != MyMoneyAccount::Income && // shouldn't be interest
+ if (acc.accountGroup() != Account::Expense && // shouldn't be fee
+ acc.accountGroup() != Account::Income && // shouldn't be interest
(sNew.value() != sNew.shares() || // shouldn't be checking account...
(sNew.value() == sNew.shares() && sNew.price() != MyMoneyMoney::ONE))) { // ...but sometimes it may look like checking account
s = sNew;
break;
}
}
}
MyMoneySplit assetAccountSplit;
QList<MyMoneySplit> feeSplits;
QList<MyMoneySplit> interestSplits;
MyMoneySecurity security;
MyMoneySecurity currency;
MyMoneySplit::investTransactionTypeE transactionType;
KMyMoneyUtils::dissectTransaction(t, s, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType);
if (!feeSplits.isEmpty()) {
feesId = feeSplits.first().accountId();
if (!interestId.isEmpty())
break;
}
if (!interestSplits.isEmpty()) {
interestId = interestSplits.first().accountId();
if (!feesId.isEmpty())
break;
}
}
} catch (const MyMoneyException &) {
}
}
void MyMoneyStatementReader::Private::scanCategories(QString& id, const MyMoneyAccount& invAcc, const MyMoneyAccount& parentAccount, const QString& defaultName)
{
if (!scannedCategories) {
previouslyUsedCategories(invAcc.id(), m_feeId, m_interestId);
scannedCategories = true;
}
if (id.isEmpty()) {
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount acc = file->accountByName(defaultName);
// if it does not exist, we have to create it
if (acc.id().isEmpty()) {
MyMoneyAccount parent = parentAccount;
acc.setName(defaultName);
acc.setAccountType(parent.accountType());
acc.setCurrencyId(parent.currencyId());
file->addAccount(acc, parent);
}
id = acc.id();
}
}
void MyMoneyStatementReader::Private::assignUniqueBankID(MyMoneySplit& s, const MyMoneyStatement::Transaction& t_in)
{
if (! t_in.m_strBankID.isEmpty()) {
// make sure that id's are unique from this point on by appending a -#
// postfix if needed
QString base(t_in.m_strBankID);
QString hash(base);
int idx = 1;
for (;;) {
QMap<QString, bool>::const_iterator it;
it = uniqIds.constFind(hash);
if (it == uniqIds.constEnd()) {
uniqIds[hash] = true;
break;
}
hash = QString("%1-%2").arg(base).arg(idx);
++idx;
}
s.setBankID(hash);
}
}
void MyMoneyStatementReader::Private::setupPrice(MyMoneySplit &s, const MyMoneyAccount &splitAccount, const MyMoneyAccount &transactionAccount, const QDate &postDate)
{
if (transactionAccount.currencyId() != splitAccount.currencyId()) {
// a currency converstion is needed assume that split has already a proper value
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneySecurity toCurrency = file->security(splitAccount.currencyId());
MyMoneySecurity fromCurrency = file->security(transactionAccount.currencyId());
// get the price for the transaction's date
const MyMoneyPrice &price = file->price(fromCurrency.id(), toCurrency.id(), postDate);
// if the price is valid calculate the shares
if (price.isValid()) {
const int fract = splitAccount.fraction(toCurrency);
const MyMoneyMoney &shares = s.value() * price.rate(toCurrency.id());
s.setShares(shares.convert(fract));
qDebug("Setting second split shares to %s", qPrintable(s.shares().formatMoney(toCurrency.id(), 2)));
} else {
qDebug("No price entry was found to convert from '%s' to '%s' on '%s'",
qPrintable(fromCurrency.tradingSymbol()), qPrintable(toCurrency.tradingSymbol()), qPrintable(postDate.toString(Qt::ISODate)));
}
}
}
MyMoneyStatementReader::MyMoneyStatementReader() :
d(new Private),
m_userAbort(false),
m_autoCreatePayee(false),
m_ft(0),
m_progressCallback(0)
{
m_askPayeeCategory = KMyMoneyGlobalSettings::askForPayeeCategory();
}
MyMoneyStatementReader::~MyMoneyStatementReader()
{
delete d;
}
bool MyMoneyStatementReader::anyTransactionAdded() const
{
return (d->transactionsAdded != 0) ? true : false;
}
void MyMoneyStatementReader::setAutoCreatePayee(bool create)
{
m_autoCreatePayee = create;
}
void MyMoneyStatementReader::setAskPayeeCategory(bool ask)
{
m_askPayeeCategory = ask;
}
bool MyMoneyStatementReader::import(const MyMoneyStatement& s, QStringList& messages)
{
//
// For testing, save the statement to an XML file
// (uncomment this line)
//
//MyMoneyStatement::writeXMLFile(s, "Imported.Xml");
//
// Select the account
//
m_account = MyMoneyAccount();
m_brokerageAccount = MyMoneyAccount();
m_ft = new MyMoneyFileTransaction();
d->m_skipCategoryMatching = s.m_skipCategoryMatching;
// if the statement source left some information about
// the account, we use it to get the current data of it
if (!s.m_accountId.isEmpty()) {
try {
m_account = MyMoneyFile::instance()->account(s.m_accountId);
} catch (const MyMoneyException &) {
qDebug("Received reference '%s' to unknown account in statement", qPrintable(s.m_accountId));
}
}
if (m_account.id().isEmpty()) {
m_account.setName(s.m_strAccountName);
m_account.setNumber(s.m_strAccountNumber);
switch (s.m_eType) {
case MyMoneyStatement::etCheckings:
- m_account.setAccountType(MyMoneyAccount::Checkings);
+ m_account.setAccountType(Account::Checkings);
break;
case MyMoneyStatement::etSavings:
- m_account.setAccountType(MyMoneyAccount::Savings);
+ m_account.setAccountType(Account::Savings);
break;
case MyMoneyStatement::etInvestment:
//testing support for investment statements!
//m_userAbort = true;
//KMessageBox::error(kmymoney, i18n("This is an investment statement. These are not supported currently."), i18n("Critical Error"));
- m_account.setAccountType(MyMoneyAccount::Investment);
+ m_account.setAccountType(Account::Investment);
break;
case MyMoneyStatement::etCreditCard:
- m_account.setAccountType(MyMoneyAccount::CreditCard);
+ m_account.setAccountType(Account::CreditCard);
break;
default:
- m_account.setAccountType(MyMoneyAccount::UnknownAccountType);
+ m_account.setAccountType(Account::Unknown);
break;
}
// we ask the user only if we have some transactions to process
if (!m_userAbort && s.m_listTransactions.count() > 0)
m_userAbort = ! selectOrCreateAccount(Select, m_account);
}
// see if we need to update some values stored with the account
if (m_account.value("lastStatementBalance") != s.m_closingBalance.toString()
|| m_account.value("lastImportedTransactionDate") != s.m_dateEnd.toString(Qt::ISODate)) {
if (s.m_closingBalance != MyMoneyMoney::autoCalc) {
m_account.setValue("lastStatementBalance", s.m_closingBalance.toString());
if (s.m_dateEnd.isValid()) {
m_account.setValue("lastImportedTransactionDate", s.m_dateEnd.toString(Qt::ISODate));
}
}
try {
MyMoneyFile::instance()->modifyAccount(m_account);
} catch (const MyMoneyException &) {
qDebug("Updating account in MyMoneyStatementReader::startImport failed");
}
}
if (!m_account.name().isEmpty())
messages += i18n("Importing statement for account %1", m_account.name());
else if (s.m_listTransactions.count() == 0)
messages += i18n("Importing statement without transactions");
qDebug("Importing statement for '%s'", qPrintable(m_account.name()));
//
// Process the securities
//
signalProgress(0, s.m_listSecurities.count(), "Importing Statement ...");
int progress = 0;
QList<MyMoneyStatement::Security>::const_iterator it_s = s.m_listSecurities.begin();
while (it_s != s.m_listSecurities.end()) {
processSecurityEntry(*it_s);
signalProgress(++progress, 0);
++it_s;
}
signalProgress(-1, -1);
//
// Process the transactions
//
if (!m_userAbort) {
try {
qDebug("Processing transactions (%s)", qPrintable(m_account.name()));
signalProgress(0, s.m_listTransactions.count(), "Importing Statement ...");
int progress = 0;
QList<MyMoneyStatement::Transaction>::const_iterator it_t = s.m_listTransactions.begin();
while (it_t != s.m_listTransactions.end() && !m_userAbort) {
processTransactionEntry(*it_t);
signalProgress(++progress, 0);
++it_t;
}
qDebug("Processing transactions done (%s)", qPrintable(m_account.name()));
} catch (const MyMoneyException &e) {
if (e.what() == "USERABORT")
m_userAbort = true;
else
qDebug("Caught exception from processTransactionEntry() not caused by USERABORT: %s", qPrintable(e.what()));
}
signalProgress(-1, -1);
}
//
// process price entries
//
if (!m_userAbort) {
try {
signalProgress(0, s.m_listPrices.count(), "Importing Statement ...");
QList<MyMoneySecurity> slist = MyMoneyFile::instance()->securityList();
QList<MyMoneySecurity>::const_iterator it_s;
for (it_s = slist.constBegin(); it_s != slist.constEnd(); ++it_s) {
d->securitiesBySymbol[(*it_s).tradingSymbol()] = *it_s;
d->securitiesByName[(*it_s).name()] = *it_s;
}
int progress = 0;
QList<MyMoneyStatement::Price>::const_iterator it_p = s.m_listPrices.begin();
while (it_p != s.m_listPrices.end()) {
processPriceEntry(*it_p);
signalProgress(++progress, 0);
++it_p;
}
} catch (const MyMoneyException &e) {
if (e.what() == "USERABORT")
m_userAbort = true;
else
qDebug("Caught exception from processPriceEntry() not caused by USERABORT: %s", qPrintable(e.what()));
}
signalProgress(-1, -1);
}
bool rc = false;
// delete all payees created in vain
int payeeCount = d->payees.count();
QList<MyMoneyPayee>::const_iterator it_p;
for (it_p = d->payees.constBegin(); it_p != d->payees.constEnd(); ++it_p) {
try {
MyMoneyFile::instance()->removePayee(*it_p);
--payeeCount;
} catch (const MyMoneyException &) {
// if we can't delete it, it must be in use which is ok for us
}
}
if (s.m_closingBalance.isAutoCalc()) {
messages += i18n(" Statement balance is not contained in statement.");
} else {
messages += i18n(" Statement balance on %1 is reported to be %2", s.m_dateEnd.toString(Qt::ISODate), s.m_closingBalance.formatMoney("", 2));
}
messages += i18n(" Transactions");
messages += i18np(" %1 processed", " %1 processed", d->transactionsCount);
messages += i18ncp("x transactions have been added", " %1 added", " %1 added", d->transactionsAdded);
messages += i18np(" %1 matched", " %1 matched", d->transactionsMatched);
messages += i18np(" %1 duplicate", " %1 duplicates", d->transactionsDuplicate);
messages += i18n(" Payees");
messages += i18ncp("x transactions have been created", " %1 created", " %1 created", payeeCount);
messages += QString();
// remove the Don't ask again entries
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup grp = config->group(QString::fromLatin1("Notification Messages"));
QStringList::ConstIterator it;
for (it = m_dontAskAgain.constBegin(); it != m_dontAskAgain.constEnd(); ++it) {
grp.deleteEntry(*it);
}
config->sync();
m_dontAskAgain.clear();
rc = !m_userAbort;
// finish the transaction
if (rc)
m_ft->commit();
delete m_ft;
m_ft = 0;
qDebug("Importing statement for '%s' done", qPrintable(m_account.name()));
return rc;
}
void MyMoneyStatementReader::processPriceEntry(const MyMoneyStatement::Price& p_in)
{
MyMoneyFile* file = MyMoneyFile::instance();
QString currency = file->baseCurrency().id();
QString security;
if (!p_in.m_strCurrency.isEmpty()) {
security = p_in.m_strSecurity;
currency = p_in.m_strCurrency;
} else if (d->securitiesBySymbol.contains(p_in.m_strSecurity)) {
security = d->securitiesBySymbol[p_in.m_strSecurity].id();
currency = file->security(file->security(security).tradingCurrency()).id();
} else if (d->securitiesByName.contains(p_in.m_strSecurity)) {
security = d->securitiesByName[p_in.m_strSecurity].id();
currency = file->security(file->security(security).tradingCurrency()).id();
} else
return;
MyMoneyPrice price(security,
currency,
p_in.m_date,
p_in.m_amount, p_in.m_sourceName.isEmpty() ? i18n("Prices Importer") : p_in.m_sourceName);
MyMoneyFile::instance()->addPrice(price);
}
void MyMoneyStatementReader::processSecurityEntry(const MyMoneyStatement::Security& sec_in)
{
// For a security entry, we will just make sure the security exists in the
// file. It will not get added to the investment account until it's called
// for in a transaction.
MyMoneyFile* file = MyMoneyFile::instance();
// check if we already have the security
// In a statement, we do not know what type of security this is, so we will
// not use type as a matching factor.
MyMoneySecurity security;
QList<MyMoneySecurity> list = file->securityList();
QList<MyMoneySecurity>::ConstIterator it = list.constBegin();
while (it != list.constEnd() && security.id().isEmpty()) {
if (matchNotEmpty(sec_in.m_strSymbol, (*it).tradingSymbol()) ||
matchNotEmpty(sec_in.m_strName, (*it).name())) {
security = *it;
}
++it;
}
// if the security was not found, we have to create it while not forgetting
// to setup the type
if (security.id().isEmpty()) {
security.setName(sec_in.m_strName);
security.setTradingSymbol(sec_in.m_strSymbol);
security.setTradingCurrency(file->baseCurrency().id());
security.setValue("kmm-security-id", sec_in.m_strId);
security.setValue("kmm-online-source", "Yahoo");
security.setSecurityType(Security::Stock);
MyMoneyFileTransaction ft;
try {
file->addSecurity(security);
ft.commit();
qDebug() << "Created " << security.name() << " with id " << security.id();
} catch (const MyMoneyException &e) {
KMessageBox::error(0, i18n("Error creating security record: %1", e.what()), i18n("Error"));
}
} else {
qDebug() << "Found " << security.name() << " with id " << security.id();
}
}
void MyMoneyStatementReader::processTransactionEntry(const MyMoneyStatement::Transaction& statementTransactionUnderImport)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyTransaction transactionUnderImport;
QString dbgMsg;
dbgMsg = QString("Process on: '%1', id: '%3', amount: '%2', fees: '%4'")
.arg(statementTransactionUnderImport.m_datePosted.toString(Qt::ISODate))
.arg(statementTransactionUnderImport.m_amount.formatMoney("", 2))
.arg(statementTransactionUnderImport.m_strBankID)
.arg(statementTransactionUnderImport.m_fees.formatMoney("", 2));
qDebug("%s", qPrintable(dbgMsg));
// mark it imported for the view
transactionUnderImport.setImported();
// TODO (Ace) We can get the commodity from the statement!!
// Although then we would need UI to verify
transactionUnderImport.setCommodity(m_account.currencyId());
transactionUnderImport.setPostDate(statementTransactionUnderImport.m_datePosted);
transactionUnderImport.setMemo(statementTransactionUnderImport.m_strMemo);
MyMoneySplit s1;
MyMoneySplit s2;
MyMoneySplit sFees;
MyMoneySplit sBrokerage;
s1.setMemo(statementTransactionUnderImport.m_strMemo);
s1.setValue(statementTransactionUnderImport.m_amount + statementTransactionUnderImport.m_fees);
s1.setShares(s1.value());
s1.setNumber(statementTransactionUnderImport.m_strNumber);
// set these values if a transfer split is needed at the very end.
MyMoneyMoney transfervalue;
// If the user has chosen to import into an investment account, determine the correct account to use
MyMoneyAccount thisaccount = m_account;
QString brokerageactid;
- if (thisaccount.accountType() == MyMoneyAccount::Investment) {
+ if (thisaccount.accountType() == Account::Investment) {
// determine the brokerage account
brokerageactid = m_account.value("kmm-brokerage-account").toUtf8();
if (brokerageactid.isEmpty()) {
brokerageactid = file->accountByName(statementTransactionUnderImport.m_strBrokerageAccount).id();
}
if (brokerageactid.isEmpty()) {
brokerageactid = file->nameToAccount(statementTransactionUnderImport.m_strBrokerageAccount);
}
if (brokerageactid.isEmpty()) {
brokerageactid = file->nameToAccount(thisaccount.brokerageName());
}
if (brokerageactid.isEmpty()) {
brokerageactid = SelectBrokerageAccount();
}
// find the security transacted, UNLESS this transaction didn't
// involve any security.
if ((statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaNone)
// eaInterest transactions MAY have a security.
// && (t_in.m_eAction != MyMoneyStatement::Transaction::eaInterest)
&& (statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaFees)) {
// the correct account is the stock account which matches two criteria:
// (1) it is a sub-account of the selected investment account, and
// (2a) the symbol of the underlying security matches the security of the
// transaction, or
// (2b) the name of the security matches the name of the security of the transaction.
// search through each subordinate account
bool found = false;
QStringList accounts = thisaccount.accountList();
QStringList::const_iterator it_account = accounts.constBegin();
QString currencyid;
while (!found && it_account != accounts.constEnd()) {
currencyid = file->account(*it_account).currencyId();
MyMoneySecurity security = file->security(currencyid);
if (matchNotEmpty(statementTransactionUnderImport.m_strSymbol, security.tradingSymbol()) ||
matchNotEmpty(statementTransactionUnderImport.m_strSecurity, security.name())) {
thisaccount = file->account(*it_account);
found = true;
}
++it_account;
}
// If there was no stock account under the m_acccount investment account,
// add one using the security.
if (!found) {
// The security should always be available, because the statement file
// should separately list all the securities referred to in the file,
// and when we found a security, we added it to the file.
if (statementTransactionUnderImport.m_strSecurity.isEmpty()) {
KMessageBox::information(0, i18n("This imported statement contains investment transactions with no security. These transactions will be ignored."), i18n("Security not found"), QString("BlankSecurity"));
return;
} else {
MyMoneySecurity security;
QList<MyMoneySecurity> list = MyMoneyFile::instance()->securityList();
QList<MyMoneySecurity>::ConstIterator it = list.constBegin();
while (it != list.constEnd() && security.id().isEmpty()) {
if (matchNotEmpty(statementTransactionUnderImport.m_strSymbol, (*it).tradingSymbol()) ||
matchNotEmpty(statementTransactionUnderImport.m_strSecurity, (*it).name())) {
security = *it;
}
++it;
}
if (!security.id().isEmpty()) {
thisaccount = MyMoneyAccount();
thisaccount.setName(security.name());
- thisaccount.setAccountType(MyMoneyAccount::Stock);
+ thisaccount.setAccountType(Account::Stock);
thisaccount.setCurrencyId(security.id());
currencyid = thisaccount.currencyId();
file->addAccount(thisaccount, m_account);
qDebug() << Q_FUNC_INFO << ": created account " << thisaccount.id() << " for security " << statementTransactionUnderImport.m_strSecurity << " under account " << m_account.id();
}
// this security does not exist in the file.
else {
// This should be rare. A statement should have a security entry for any
// of the securities referred to in the transactions. The only way to get
// here is if that's NOT the case.
int ret = KMessageBox::warningContinueCancel(0, i18n("<center>This investment account does not contain the \"%1\" security.</center>"
"<center>Transactions involving this security will be ignored.</center>", statementTransactionUnderImport.m_strSecurity),
i18n("Security not found"), KStandardGuiItem::cont(), KStandardGuiItem::cancel());
if (ret == KMessageBox::Cancel) {
m_userAbort = true;
}
return;
}
}
}
// Don't update price if there is no price information contained in the transaction
if (statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaCashDividend
&& statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaShrsin
&& statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaShrsout) {
// update the price, while we're here. in the future, this should be
// an option
QString basecurrencyid = file->baseCurrency().id();
const MyMoneyPrice &price = file->price(currencyid, basecurrencyid, statementTransactionUnderImport.m_datePosted, true);
if (!price.isValid() && ((!statementTransactionUnderImport.m_amount.isZero() && !statementTransactionUnderImport.m_shares.isZero()) || !statementTransactionUnderImport.m_price.isZero())) {
MyMoneyPrice newprice;
if (!statementTransactionUnderImport.m_price.isZero()) {
newprice = MyMoneyPrice(currencyid, basecurrencyid, statementTransactionUnderImport.m_datePosted,
statementTransactionUnderImport.m_price.abs(), i18n("Statement Importer"));
} else {
newprice = MyMoneyPrice(currencyid, basecurrencyid, statementTransactionUnderImport.m_datePosted,
(statementTransactionUnderImport.m_amount / statementTransactionUnderImport.m_shares).abs(), i18n("Statement Importer"));
}
file->addPrice(newprice);
}
}
}
s1.setAccountId(thisaccount.id());
d->assignUniqueBankID(s1, statementTransactionUnderImport);
if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaReinvestDividend) {
s1.setAction(MyMoneySplit::ActionReinvestDividend);
s1.setShares(statementTransactionUnderImport.m_shares);
if (!statementTransactionUnderImport.m_price.isZero()) {
s1.setPrice(statementTransactionUnderImport.m_price);
} else {
if (statementTransactionUnderImport.m_shares.isZero()) {
KMessageBox::information(0, i18n("This imported statement contains investment transactions with no share amount. These transactions will be ignored."), i18n("No share amount provided"), QString("BlankAmount"));
return;
}
MyMoneyMoney total = -statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees;
s1.setPrice((total / statementTransactionUnderImport.m_shares).convertPrecision(file->security(thisaccount.currencyId()).pricePrecision()));
}
s2.setMemo(statementTransactionUnderImport.m_strMemo);
if (statementTransactionUnderImport.m_strInterestCategory.isEmpty())
s2.setAccountId(d->interestId(thisaccount));
else
s2.setAccountId(d->interestId(statementTransactionUnderImport.m_strInterestCategory));
s2.setShares(-statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees);
s2.setValue(s2.shares());
} else if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaCashDividend) {
// Cash dividends require setting 2 splits to get all of the information
// in. Split #1 will be the income split, and we'll set it to the first
// income account. This is a hack, but it's needed in order to get the
// amount into the transaction.
if (statementTransactionUnderImport.m_strInterestCategory.isEmpty())
s1.setAccountId(d->interestId(thisaccount));
else {// Ensure category sub-accounts are dealt with properly
s1.setAccountId(d->interestId(statementTransactionUnderImport.m_strInterestCategory));
}
s1.setShares(-statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees);
s1.setValue(-statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees);
// Split 2 will be the zero-amount investment split that serves to
// mark this transaction as a cash dividend and note which stock account
// it belongs to.
s2.setMemo(statementTransactionUnderImport.m_strMemo);
s2.setAction(MyMoneySplit::ActionDividend);
s2.setAccountId(thisaccount.id());
/* at this point any fees have been taken into account already
* so don't deduct them again.
* BUG 322381
*/
transfervalue = statementTransactionUnderImport.m_amount;
} else if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaInterest) {
if (statementTransactionUnderImport.m_strInterestCategory.isEmpty())
s1.setAccountId(d->interestId(thisaccount));
else {// Ensure category sub-accounts are dealt with properly
if (statementTransactionUnderImport.m_amount.isPositive())
s1.setAccountId(d->interestId(statementTransactionUnderImport.m_strInterestCategory));
else
s1.setAccountId(d->expenseId(statementTransactionUnderImport.m_strInterestCategory));
}
s1.setShares(-statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees);
s1.setValue(-statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees);
/// *********** Add split as per Div **********
// Split 2 will be the zero-amount investment split that serves to
// mark this transaction as a cash dividend and note which stock account
// it belongs to.
s2.setMemo(statementTransactionUnderImport.m_strMemo);
s2.setAction(MyMoneySplit::ActionInterestIncome);
s2.setAccountId(thisaccount.id());
transfervalue = statementTransactionUnderImport.m_amount;
} else if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaFees) {
if (statementTransactionUnderImport.m_strInterestCategory.isEmpty())
s1.setAccountId(d->feeId(thisaccount));
else// Ensure category sub-accounts are dealt with properly
s1.setAccountId(d->feeId(statementTransactionUnderImport.m_strInterestCategory));
s1.setShares(statementTransactionUnderImport.m_amount);
s1.setValue(statementTransactionUnderImport.m_amount);
transfervalue = statementTransactionUnderImport.m_amount;
} else if ((statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaBuy) ||
(statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaSell)) {
s1.setAction(MyMoneySplit::ActionBuyShares);
if (!statementTransactionUnderImport.m_price.isZero()) {
s1.setPrice(statementTransactionUnderImport.m_price.abs());
} else if (!statementTransactionUnderImport.m_shares.isZero()) {
MyMoneyMoney total = statementTransactionUnderImport.m_amount + statementTransactionUnderImport.m_fees.abs();
s1.setPrice((total / statementTransactionUnderImport.m_shares).abs().convertPrecision(file->security(thisaccount.currencyId()).pricePrecision()));
}
if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaBuy)
s1.setShares(statementTransactionUnderImport.m_shares.abs());
else
s1.setShares(-statementTransactionUnderImport.m_shares.abs());
s1.setValue(-(statementTransactionUnderImport.m_amount + statementTransactionUnderImport.m_fees.abs()));
transfervalue = statementTransactionUnderImport.m_amount;
} else if ((statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaShrsin) ||
(statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaShrsout)) {
s1.setValue(MyMoneyMoney());
s1.setShares(statementTransactionUnderImport.m_shares);
s1.setAction(MyMoneySplit::ActionAddShares);
} else if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaNone) {
// User is attempting to import a non-investment transaction into this
// investment account. This is not supportable the way KMyMoney is
// written. However, if a user has an associated brokerage account,
// we can stuff the transaction there.
QString brokerageactid = m_account.value("kmm-brokerage-account").toUtf8();
if (brokerageactid.isEmpty()) {
brokerageactid = file->accountByName(m_account.brokerageName()).id();
}
if (! brokerageactid.isEmpty()) {
s1.setAccountId(brokerageactid);
d->assignUniqueBankID(s1, statementTransactionUnderImport);
// Needed to satisfy the bankid check below.
thisaccount = file->account(brokerageactid);
} else {
// Warning!! Your transaction is being thrown away.
}
}
if (!statementTransactionUnderImport.m_fees.isZero()) {
sFees.setMemo(i18n("(Fees) %1", statementTransactionUnderImport.m_strMemo));
sFees.setValue(statementTransactionUnderImport.m_fees);
sFees.setShares(statementTransactionUnderImport.m_fees);
sFees.setAccountId(d->feeId(thisaccount));
}
} else {
// For non-investment accounts, just use the selected account
// Note that it is perfectly reasonable to import an investment statement into a non-investment account
// if you really want. The investment-specific information, such as number of shares and action will
// be discarded in that case.
s1.setAccountId(m_account.id());
d->assignUniqueBankID(s1, statementTransactionUnderImport);
}
QString payeename = statementTransactionUnderImport.m_strPayee;
if (!payeename.isEmpty()) {
qDebug() << QLatin1String("Start matching payee") << payeename;
QString payeeid;
try {
QList<MyMoneyPayee> pList = file->payeeList();
QList<MyMoneyPayee>::const_iterator it_p;
QMap<int, QString> matchMap;
for (it_p = pList.constBegin(); it_p != pList.constEnd(); ++it_p) {
bool ignoreCase;
QStringList keys;
QStringList::const_iterator it_s;
const MyMoneyPayee::payeeMatchType matchType = (*it_p).matchData(ignoreCase, keys);
switch (matchType) {
case MyMoneyPayee::matchDisabled:
break;
case MyMoneyPayee::matchName:
case MyMoneyPayee::matchNameExact:
keys << QString("%1").arg(QRegExp::escape((*it_p).name()));
if(matchType == MyMoneyPayee::matchNameExact) {
keys.clear();
keys << QString("^%1$").arg(QRegExp::escape((*it_p).name()));
}
// intentional fall through
case MyMoneyPayee::matchKey:
for (it_s = keys.constBegin(); it_s != keys.constEnd(); ++it_s) {
QRegExp exp(*it_s, ignoreCase ? Qt::CaseInsensitive : Qt::CaseSensitive);
if (exp.indexIn(payeename) != -1) {
qDebug("Found match with '%s' on '%s'", qPrintable(payeename), qPrintable((*it_p).name()));
matchMap[exp.matchedLength()] = (*it_p).id();
}
}
break;
}
}
// at this point we can have several scenarios:
// a) multiple matches
// b) a single match
// c) no match at all
//
// for c) we just do nothing, for b) we take the one we found
// in case of a) we take the one with the largest matchedLength()
// which happens to be the last one in the map
if (matchMap.count() > 1) {
qDebug("Multiple matches");
QMap<int, QString>::const_iterator it_m = matchMap.constEnd();
--it_m;
payeeid = *it_m;
} else if (matchMap.count() == 1) {
qDebug("Single matches");
payeeid = *(matchMap.constBegin());
}
// if we did not find a matching payee, we throw an exception and try to create it
if (payeeid.isEmpty())
throw MYMONEYEXCEPTION("payee not matched");
s1.setPayeeId(payeeid);
} catch (const MyMoneyException &) {
MyMoneyPayee payee;
int rc = KMessageBox::Yes;
if (m_autoCreatePayee == false) {
// Ask the user if that is what he intended to do?
QString msg = i18n("Do you want to add \"%1\" as payee/receiver?\n\n", payeename);
msg += i18n("Selecting \"Yes\" will create the payee, \"No\" will skip "
"creation of a payee record and remove the payee information "
"from this transaction. Selecting \"Cancel\" aborts the import "
"operation.\n\nIf you select \"No\" here and mark the \"Do not ask "
"again\" checkbox, the payee information for all following transactions "
"referencing \"%1\" will be removed.", payeename);
QString askKey = QString("Statement-Import-Payee-") + payeename;
if (!m_dontAskAgain.contains(askKey)) {
m_dontAskAgain += askKey;
}
rc = KMessageBox::questionYesNoCancel(0, msg, i18n("New payee/receiver"),
KStandardGuiItem::yes(), KStandardGuiItem::no(), KStandardGuiItem::cancel(), askKey);
}
if (rc == KMessageBox::Yes) {
// for now, we just add the payee to the pool and turn
// on simple name matching, so that future transactions
// with the same name don't get here again.
//
// In the future, we could open a dialog and ask for
// all the other attributes of the payee, but since this
// is called in the context of an automatic procedure it
// might distract the user.
payee.setName(payeename);
payee.setMatchData(MyMoneyPayee::matchKey, true, QStringList() << QString("^%1$").arg(QRegExp::escape(payeename)));
if (m_askPayeeCategory) {
// We use a QPointer because the dialog may get deleted
// during exec() if the parent of the dialog gets deleted.
// In that case the guarded ptr will reset to 0.
QPointer<QDialog> dialog = new QDialog(kmymoney);
dialog->setWindowTitle(i18n("Default Category for Payee"));
dialog->setModal(true);
QWidget *mainWidget = new QWidget;
QVBoxLayout *topcontents = new QVBoxLayout(mainWidget);
//add in caption? and account combo here
QLabel *label1 = new QLabel(i18n("Please select a default category for payee '%1'", payeename));
topcontents->addWidget(label1);
auto filterProxyModel = new AccountNamesFilterProxyModel(this);
filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode());
- filterProxyModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Asset, MyMoneyAccount::Liability, MyMoneyAccount::Equity, MyMoneyAccount::Income, MyMoneyAccount::Expense});
+ filterProxyModel->addAccountGroup(QVector<Account> {Account::Asset, Account::Liability, Account::Equity, Account::Income, Account::Expense});
auto const model = Models::instance()->accountsModel();
filterProxyModel->setSourceModel(model);
filterProxyModel->setSourceColumns(model->getColumns());
filterProxyModel->sort((int)eAccountsModel::Column::Account);
QPointer<KMyMoneyAccountCombo> accountCombo = new KMyMoneyAccountCombo(filterProxyModel);
topcontents->addWidget(accountCombo);
mainWidget->setLayout(topcontents);
QVBoxLayout *mainLayout = new QVBoxLayout;
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::No|QDialogButtonBox::Yes);
dialog->setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
dialog->connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
dialog->connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
mainLayout->addWidget(buttonBox);
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Yes), KGuiItem(i18n("Save Category")));
KGuiItem::assign(buttonBox->button(QDialogButtonBox::No), KGuiItem(i18n("No Category")));
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KGuiItem(i18n("Abort")));
int result = dialog->exec();
QString accountId;
if (accountCombo && !accountCombo->getSelected().isEmpty()) {
accountId = accountCombo->getSelected();
}
delete dialog;
//if they hit yes instead of no, then grab setting of account combo
if (result == QDialog::Accepted) {
payee.setDefaultAccountId(accountId);
} else if (result != QDialog::Rejected) {
//add cancel button? and throw exception like below
throw MYMONEYEXCEPTION("USERABORT");
}
}
try {
file->addPayee(payee);
qDebug("Payee '%s' created", qPrintable(payee.name()));
d->payees << payee;
payeeid = payee.id();
s1.setPayeeId(payeeid);
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to add payee/receiver"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
}
} else if (rc == KMessageBox::No) {
s1.setPayeeId(QString());
} else {
throw MYMONEYEXCEPTION("USERABORT");
}
}
- if (thisaccount.accountType() != MyMoneyAccount::Stock) {
+ if (thisaccount.accountType() != Account::Stock) {
//
// Fill in other side of the transaction (category/etc) based on payee
//
// Note, this logic is lifted from KLedgerView::slotPayeeChanged(),
// however this case is more complicated, because we have an amount and
// a memo. We just don't have the other side of the transaction.
//
// We'll search for the most recent transaction in this account with
// this payee. If this reference transaction is a simple 2-split
// transaction, it's simple. If it's a complex split, and the amounts
// are different, we have a problem. Somehow we have to balance the
// transaction. For now, we'll leave it unbalanced, and let the user
// handle it.
//
const MyMoneyPayee& payeeObj = MyMoneyFile::instance()->payee(payeeid);
if (statementTransactionUnderImport.m_listSplits.isEmpty() && payeeObj.defaultAccountEnabled()) {
MyMoneyAccount splitAccount = file->account(payeeObj.defaultAccountId());
MyMoneySplit s;
s.setReconcileFlag(MyMoneySplit::Cleared);
s.clearId();
s.setBankID(QString());
s.setShares(-s1.shares());
s.setValue(-s1.value());
s.setAccountId(payeeObj.defaultAccountId());
s.setMemo(transactionUnderImport.memo());
s.setPayeeId(payeeid);
d->setupPrice(s, splitAccount, m_account, statementTransactionUnderImport.m_datePosted);
transactionUnderImport.addSplit(s);
file->addVATSplit(transactionUnderImport, m_account, splitAccount, statementTransactionUnderImport.m_amount);
} else if (statementTransactionUnderImport.m_listSplits.isEmpty() && !d->m_skipCategoryMatching) {
MyMoneyTransactionFilter filter(thisaccount.id());
filter.addPayee(payeeid);
QList<MyMoneyTransaction> list = file->transactionList(filter);
if (!list.empty()) {
// Default to using the most recent transaction as the reference
MyMoneyTransaction t_old = list.last();
// if there is more than one matching transaction, try to be a little
// smart about which one we take. for now, we'll see if there's one
// with the same VALUE as our imported transaction, and if so take that one.
if (list.count() > 1) {
QList<MyMoneyTransaction>::ConstIterator it_trans = list.constEnd();
if (it_trans != list.constBegin())
--it_trans;
while (it_trans != list.constBegin()) {
MyMoneySplit s = (*it_trans).splitByAccount(thisaccount.id());
if (s.value() == s1.value()) {
// keep searching if this transaction references a closed account
if (!MyMoneyFile::instance()->referencesClosedAccount(*it_trans)) {
t_old = *it_trans;
break;
}
}
--it_trans;
}
// check constBegin, just in case
if (it_trans == list.constBegin()) {
MyMoneySplit s = (*it_trans).splitByAccount(thisaccount.id());
if (s.value() == s1.value()) {
t_old = *it_trans;
}
}
}
// Only copy the splits if the transaction found does not reference a closed account
if (!MyMoneyFile::instance()->referencesClosedAccount(t_old)) {
QList<MyMoneySplit>::ConstIterator it_split;
for (it_split = t_old.splits().constBegin(); it_split != t_old.splits().constEnd(); ++it_split) {
// We don't need the split that covers this account,
// we just need the other ones.
if ((*it_split).accountId() != thisaccount.id()) {
MyMoneySplit s(*it_split);
s.setReconcileFlag(MyMoneySplit::NotReconciled);
s.clearId();
s.setBankID(QString());
s.removeMatch();
if (t_old.splits().count() == 2) {
s.setShares(-s1.shares());
s.setValue(-s1.value());
s.setMemo(s1.memo());
}
MyMoneyAccount splitAccount = file->account(s.accountId());
qDebug("Adding second split to %s(%s)",
qPrintable(splitAccount.name()),
qPrintable(s.accountId()));
d->setupPrice(s, splitAccount, m_account, statementTransactionUnderImport.m_datePosted);
transactionUnderImport.addSplit(s);
}
}
}
}
}
}
}
s1.setReconcileFlag(statementTransactionUnderImport.m_reconcile);
// Add the 'account' split if it's needed
if (! transfervalue.isZero()) {
// in case the transaction has a reference to the brokerage account, we use it
// but if brokerageactid has already been set, keep that.
if (!statementTransactionUnderImport.m_strBrokerageAccount.isEmpty() && brokerageactid.isEmpty()) {
brokerageactid = file->nameToAccount(statementTransactionUnderImport.m_strBrokerageAccount);
}
if (brokerageactid.isEmpty()) {
brokerageactid = file->accountByName(statementTransactionUnderImport.m_strBrokerageAccount).id();
}
// There is no BrokerageAccount so have to nowhere to put this split.
if (!brokerageactid.isEmpty()) {
sBrokerage.setMemo(statementTransactionUnderImport.m_strMemo);
sBrokerage.setValue(transfervalue);
sBrokerage.setShares(transfervalue);
sBrokerage.setAccountId(brokerageactid);
sBrokerage.setReconcileFlag(statementTransactionUnderImport.m_reconcile);
MyMoneyAccount splitAccount = file->account(sBrokerage.accountId());
d->setupPrice(sBrokerage, splitAccount, m_account, statementTransactionUnderImport.m_datePosted);
}
}
if (!(sBrokerage == MyMoneySplit()))
transactionUnderImport.addSplit(sBrokerage);
if (!(sFees == MyMoneySplit()))
transactionUnderImport.addSplit(sFees);
if (!(s2 == MyMoneySplit()))
transactionUnderImport.addSplit(s2);
transactionUnderImport.addSplit(s1);
if ((statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaReinvestDividend) && (statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaCashDividend) && (statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaInterest)
) {
//******************************************
// process splits
//******************************************
QList<MyMoneyStatement::Split>::const_iterator it_s;
for (it_s = statementTransactionUnderImport.m_listSplits.begin(); it_s != statementTransactionUnderImport.m_listSplits.end(); ++it_s) {
MyMoneySplit s3;
s3.setAccountId((*it_s).m_accountId);
MyMoneyAccount acc = file->account(s3.accountId());
s3.setPayeeId(s1.payeeId());
s3.setMemo((*it_s).m_strMemo);
s3.setShares((*it_s).m_amount);
s3.setValue((*it_s).m_amount);
s3.setReconcileFlag((*it_s).m_reconcile);
d->setupPrice(s3, acc, m_account, statementTransactionUnderImport.m_datePosted);
transactionUnderImport.addSplit(s3);
}
}
// Add the transaction
try {
// check for matches already stored in the engine
TransactionMatchFinder::MatchResult result;
TransactionMatcher matcher(thisaccount);
d->transactionsCount++;
ExistingTransactionMatchFinder existingTrMatchFinder(KMyMoneyGlobalSettings::matchInterval());
result = existingTrMatchFinder.findMatch(transactionUnderImport, s1);
if (result != TransactionMatchFinder::MatchNotFound) {
MyMoneyTransaction matchedTransaction = existingTrMatchFinder.getMatchedTransaction();
if (!matchedTransaction.isImported() || result == TransactionMatchFinder::MatchPrecise) { // don't match with just imported transaction
MyMoneySplit matchedSplit = existingTrMatchFinder.getMatchedSplit();
handleMatchingOfExistingTransaction(matcher, matchedTransaction, matchedSplit, transactionUnderImport, s1, result);
return;
}
}
addTransaction(transactionUnderImport);
ScheduledTransactionMatchFinder scheduledTrMatchFinder(thisaccount, KMyMoneyGlobalSettings::matchInterval());
result = scheduledTrMatchFinder.findMatch(transactionUnderImport, s1);
if (result != TransactionMatchFinder::MatchNotFound) {
MyMoneySplit matchedSplit = scheduledTrMatchFinder.getMatchedSplit();
MyMoneySchedule matchedSchedule = scheduledTrMatchFinder.getMatchedSchedule();
handleMatchingOfScheduledTransaction(matcher, matchedSchedule, matchedSplit, transactionUnderImport, s1);
return;
}
} catch (const MyMoneyException &e) {
QString message(i18n("Problem adding or matching imported transaction with id '%1': %2", statementTransactionUnderImport.m_strBankID, e.what()));
qDebug("%s", qPrintable(message));
int result = KMessageBox::warningContinueCancel(0, message);
if (result == KMessageBox::Cancel)
throw MYMONEYEXCEPTION("USERABORT");
}
}
QString MyMoneyStatementReader::SelectBrokerageAccount()
{
if (m_brokerageAccount.id().isEmpty()) {
- m_brokerageAccount.setAccountType(MyMoneyAccount::Checkings);
+ m_brokerageAccount.setAccountType(Account::Checkings);
if (!m_userAbort)
m_userAbort = ! selectOrCreateAccount(Select, m_brokerageAccount);
}
return m_brokerageAccount.id();
}
bool MyMoneyStatementReader::selectOrCreateAccount(const SelectCreateMode /*mode*/, MyMoneyAccount& account)
{
bool result = false;
MyMoneyFile* file = MyMoneyFile::instance();
QString accountId;
// Try to find an existing account in the engine which matches this one.
// There are two ways to be a "matching account". The account number can
// match the statement account OR the "StatementKey" property can match.
// Either way, we'll update the "StatementKey" property for next time.
QString accountNumber = account.number();
if (! accountNumber.isEmpty()) {
// Get a list of all accounts
QList<MyMoneyAccount> accounts;
file->accountList(accounts);
// Iterate through them
QList<MyMoneyAccount>::const_iterator it_account = accounts.constBegin();
while (it_account != accounts.constEnd()) {
if (
((*it_account).value("StatementKey") == accountNumber) ||
((*it_account).number() == accountNumber)
) {
MyMoneyAccount newAccount((*it_account).id(), account);
account = newAccount;
accountId = (*it_account).id();
break;
}
++it_account;
}
}
QString msg = i18n("<b>You have downloaded a statement for the following account:</b><br/><br/>");
msg += i18n(" - Account Name: %1", account.name()) + "<br/>";
msg += i18n(" - Account Type: %1", KMyMoneyUtils::accountTypeToString(account.accountType())) + "<br/>";
msg += i18n(" - Account Number: %1", account.number()) + "<br/>";
msg += "<br/>";
if (!account.name().isEmpty()) {
if (!accountId.isEmpty())
msg += i18n("Do you want to import transactions to this account?");
else
msg += i18n("KMyMoney cannot determine which of your accounts to use. You can "
"create a new account by pressing the <b>Create</b> button "
"or select another one manually from the selection box below.");
} else {
msg += i18n("No account information has been found in the selected statement file. "
"Please select an account using the selection box in the dialog or "
"create a new account by pressing the <b>Create</b> button.");
}
KMyMoneyUtils::categoryTypeE type;
- if (account.accountType() == MyMoneyAccount::Checkings) {
+ if (account.accountType() == Account::Checkings) {
type = static_cast<KMyMoneyUtils::categoryTypeE>(KMyMoneyUtils::checking);
- } else if (account.accountType() == MyMoneyAccount::Savings) {
+ } else if (account.accountType() == Account::Savings) {
type = static_cast<KMyMoneyUtils::categoryTypeE>(KMyMoneyUtils::savings);
- } else if (account.accountType() == MyMoneyAccount::Investment) {
+ } else if (account.accountType() == Account::Investment) {
type = static_cast<KMyMoneyUtils::categoryTypeE>(KMyMoneyUtils::investment);
- } else if (account.accountType() == MyMoneyAccount::CreditCard) {
+ } else if (account.accountType() == Account::CreditCard) {
type = static_cast<KMyMoneyUtils::categoryTypeE>(KMyMoneyUtils::creditCard);
} else {
type = static_cast<KMyMoneyUtils::categoryTypeE>(KMyMoneyUtils::asset | KMyMoneyUtils::liability);
}
QPointer<KAccountSelectDlg> accountSelect = new KAccountSelectDlg(type, "StatementImport", kmymoney);
accountSelect->setHeader(i18n("Import transactions"));
accountSelect->setDescription(msg);
accountSelect->setAccount(account, accountId);
accountSelect->setMode(false);
accountSelect->showAbortButton(true);
accountSelect->m_qifEntry->hide();
QString accname;
bool done = false;
while (!done) {
if (accountSelect->exec() == QDialog::Accepted && !accountSelect->selectedAccount().isEmpty()) {
result = true;
done = true;
accountId = accountSelect->selectedAccount();
account = file->account(accountId);
if (! accountNumber.isEmpty() && account.value("StatementKey") != accountNumber) {
account.setValue("StatementKey", accountNumber);
MyMoneyFileTransaction ft;
try {
MyMoneyFile::instance()->modifyAccount(account);
ft.commit();
accname = account.name();
} catch (const MyMoneyException &) {
qDebug("Updating account in MyMoneyStatementReader::selectOrCreateAccount failed");
}
}
} else {
if (accountSelect->aborted())
//throw MYMONEYEXCEPTION("USERABORT");
done = true;
else
KMessageBox::error(0, QLatin1String("<html>") + i18n("You must select an account, create a new one, or press the <b>Abort</b> button.") + QLatin1String("</html>"));
}
}
delete accountSelect;
return result;
}
void MyMoneyStatementReader::setProgressCallback(void(*callback)(int, int, const QString&))
{
m_progressCallback = callback;
}
void MyMoneyStatementReader::signalProgress(int current, int total, const QString& msg)
{
if (m_progressCallback != 0)
(*m_progressCallback)(current, total, msg);
}
void MyMoneyStatementReader::handleMatchingOfExistingTransaction(TransactionMatcher & matcher,
MyMoneyTransaction matchedTransaction,
MyMoneySplit matchedSplit,
MyMoneyTransaction & importedTransaction,
const MyMoneySplit & importedSplit,
const TransactionMatchFinder::MatchResult & matchResult)
{
switch (matchResult) {
case TransactionMatchFinder::MatchNotFound:
break;
case TransactionMatchFinder::MatchDuplicate:
d->transactionsDuplicate++;
qDebug("Detected transaction duplicate");
break;
case TransactionMatchFinder::MatchImprecise:
case TransactionMatchFinder::MatchPrecise:
addTransaction(importedTransaction);
qDebug("Detected as match to transaction '%s'", qPrintable(matchedTransaction.id()));
matcher.match(matchedTransaction, matchedSplit, importedTransaction, importedSplit, true);
d->transactionsMatched++;
break;
}
}
void MyMoneyStatementReader::handleMatchingOfScheduledTransaction(TransactionMatcher & matcher,
MyMoneySchedule matchedSchedule,
MyMoneySplit matchedSplit,
const MyMoneyTransaction & importedTransaction,
const MyMoneySplit & importedSplit)
{
QPointer<TransactionEditor> editor;
if (askUserToEnterScheduleForMatching(matchedSchedule, importedSplit, importedTransaction)) {
KEnterScheduleDlg dlg(0, matchedSchedule);
editor = dlg.startEdit();
if (editor) {
MyMoneyTransaction torig;
try {
// in case the amounts of the scheduled transaction and the
// imported transaction differ, we need to update the amount
// using the transaction editor.
if (matchedSplit.shares() != importedSplit.shares() && !matchedSchedule.isFixed()) {
// for now this only works with regular transactions and not
// for investment transactions. As of this, we don't have
// scheduled investment transactions anyway.
StdTransactionEditor* se = dynamic_cast<StdTransactionEditor*>(editor.data());
if (se) {
// the following call will update the amount field in the
// editor and also adjust a possible VAT assignment. Make
// sure to use only the absolute value of the amount, because
// the editor keeps the sign in a different position (deposit,
// withdrawal tab)
kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(se->haveWidget("amount"));
if (amount) {
amount->setValue(importedSplit.shares().abs());
se->slotUpdateAmount(importedSplit.shares().abs().toString());
// we also need to update the matchedSplit variable to
// have the modified share/value.
matchedSplit.setShares(importedSplit.shares());
matchedSplit.setValue(importedSplit.value());
}
}
}
editor->createTransaction(torig, dlg.transaction(), dlg.transaction().splits().isEmpty() ? MyMoneySplit() : dlg.transaction().splits().front(), true);
QString newId;
if (editor->enterTransactions(newId, false, true)) {
if (!newId.isEmpty()) {
torig = MyMoneyFile::instance()->transaction(newId);
matchedSchedule.setLastPayment(torig.postDate());
}
matchedSchedule.setNextDueDate(matchedSchedule.nextPayment(matchedSchedule.nextDueDate()));
MyMoneyFile::instance()->modifySchedule(matchedSchedule);
}
// now match the two transactions
matcher.match(torig, matchedSplit, importedTransaction, importedSplit);
d->transactionsMatched++;
} catch (const MyMoneyException &e) {
// make sure we get rid of the editor before
// the KEnterScheduleDlg is destroyed
delete editor;
throw e; // rethrow
}
}
// delete the editor
delete editor;
}
}
void MyMoneyStatementReader::addTransaction(MyMoneyTransaction& transaction)
{
MyMoneyFile* file = MyMoneyFile::instance();
file->addTransaction(transaction);
d->transactionsAdded++;
}
bool MyMoneyStatementReader::askUserToEnterScheduleForMatching(const MyMoneySchedule& matchedSchedule, const MyMoneySplit& importedSplit, const MyMoneyTransaction & importedTransaction) const
{
QString scheduleName = matchedSchedule.name();
int currencyDenom = m_account.fraction(MyMoneyFile::instance()->currency(m_account.currencyId()));
QString splitValue = importedSplit.value().formatMoney(currencyDenom);
QString payeeName = MyMoneyFile::instance()->payee(importedSplit.payeeId()).name();
QString questionMsg = i18n("KMyMoney has found a scheduled transaction which matches an imported transaction.<br/>"
"Schedule name: <b>%1</b><br/>"
"Transaction: <i>%2 %3</i><br/>"
"Do you want KMyMoney to enter this schedule now so that the transaction can be matched?",
scheduleName, splitValue, payeeName);
// check that dates are within user's setting
const int gap = std::abs(matchedSchedule.transaction().postDate().toJulianDay() - importedTransaction.postDate().toJulianDay());
if (gap > KMyMoneyGlobalSettings::matchInterval())
questionMsg = i18np("KMyMoney has found a scheduled transaction which matches an imported transaction.<br/>"
"Schedule name: <b>%2</b><br/>"
"Transaction: <i>%3 %4</i><br/>"
"The transaction dates are one day apart.<br/>"
"Do you want KMyMoney to enter this schedule now so that the transaction can be matched?",
"KMyMoney has found a scheduled transaction which matches an imported transaction.<br/>"
"Schedule name: <b>%2</b><br/>"
"Transaction: <i>%3 %4</i><br/>"
"The transaction dates are %1 days apart.<br/>"
"Do you want KMyMoney to enter this schedule now so that the transaction can be matched?",
gap ,scheduleName, splitValue, payeeName);
const int userAnswer = KMessageBox::questionYesNo(0, QLatin1String("<html>") + questionMsg + QLatin1String("</html>"), i18n("Schedule found"));
return (userAnswer == KMessageBox::Yes);
}
diff --git a/kmymoney/converter/mymoneytemplate.cpp b/kmymoney/converter/mymoneytemplate.cpp
index 8b8878ef3..126d2faa9 100644
--- a/kmymoney/converter/mymoneytemplate.cpp
+++ b/kmymoney/converter/mymoneytemplate.cpp
@@ -1,486 +1,486 @@
/***************************************************************************
mymoneytemplate.cpp - description
-------------------
begin : Sat Aug 14 2004
copyright : (C) 2004 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 "mymoneytemplate.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QList>
#include <QSaveFile>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KMessageBox>
#include <QTemporaryFile>
#include <KXmlGuiWindow>
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneyutils.h"
#include "mymoneyfile.h"
MyMoneyTemplate::MyMoneyTemplate() :
m_progressCallback(0),
m_accountsRead(0)
{
}
MyMoneyTemplate::MyMoneyTemplate(const QUrl& url) :
m_progressCallback(0),
m_accountsRead(0)
{
loadTemplate(url);
}
MyMoneyTemplate::~MyMoneyTemplate()
{
}
bool MyMoneyTemplate::loadTemplate(const QUrl &url)
{
QString filename;
if (!url.isValid()) {
qDebug("Invalid template URL '%s'", qPrintable(url.url()));
return false;
}
m_source = url;
if (url.isLocalFile()) {
filename = url.toLocalFile();
} else {
bool rc = false;
// TODO: port to kf5
//rc = KIO::NetAccess::download(url, filename, KMyMoneyUtils::mainWindow());
if (!rc) {
KMessageBox::detailedError(KMyMoneyUtils::mainWindow(),
i18n("Error while loading file '%1'.", url.url()),
// TODO: port to kf5
QString(),//KIO::NetAccess::lastErrorString(),
i18n("File access error"));
return false;
}
}
bool rc = true;
QFile file(filename);
QFileInfo info(file);
if (!info.isFile()) {
QString msg = i18n("<p><b>%1</b> is not a template file.</p>", filename);
KMessageBox::error(KMyMoneyUtils::mainWindow(), msg, i18n("Filetype Error"));
return false;
}
if (file.open(QIODevice::ReadOnly)) {
QString errMsg;
int errLine, errColumn;
if (!m_doc.setContent(&file, &errMsg, &errLine, &errColumn)) {
QString msg = i18n("<p>Error while reading template file <b>%1</b> in line %2, column %3</p>", filename, errLine, errColumn);
KMessageBox::detailedError(KMyMoneyUtils::mainWindow(), msg, errMsg, i18n("Template Error"));
rc = false;
} else {
rc = loadDescription();
}
file.close();
} else {
KMessageBox::sorry(KMyMoneyUtils::mainWindow(), i18n("File '%1' not found.", filename));
rc = false;
}
// if a temporary file was constructed by NetAccess::download,
// then it will be removed with the next call. Otherwise, it
// stays untouched on the local filesystem
// TODO: port to kf5
//KIO::NetAccess::removeTempFile(filename);
return rc;
}
bool MyMoneyTemplate::loadDescription()
{
int validMask = 0x00;
const int validAccount = 0x01;
const int validTitle = 0x02;
const int validShort = 0x04;
const int validLong = 0x08;
const int invalid = 0x10;
const int validHeader = 0x0F;
QDomElement rootElement = m_doc.documentElement();
if (!rootElement.isNull()
&& rootElement.tagName() == "kmymoney-account-template") {
QDomNode child = rootElement.firstChild();
while (!child.isNull() && child.isElement()) {
QDomElement childElement = child.toElement();
// qDebug("MyMoneyTemplate::import: Processing child node %s", childElement.tagName().data());
if (childElement.tagName() == "accounts") {
m_accounts = childElement.firstChild();
validMask |= validAccount;
} else if (childElement.tagName() == "title") {
m_title = childElement.text();
validMask |= validTitle;
} else if (childElement.tagName() == "shortdesc") {
m_shortDesc = childElement.text();
validMask |= validShort;
} else if (childElement.tagName() == "longdesc") {
m_longDesc = childElement.text();
validMask |= validLong;
} else {
KMessageBox::error(KMyMoneyUtils::mainWindow(), i18n("<p>Invalid tag <b>%1</b> in template file <b>%2</b></p>", childElement.tagName(), m_source.toDisplayString()));
validMask |= invalid;
}
child = child.nextSibling();
}
}
return validMask == validHeader;
}
bool MyMoneyTemplate::hierarchy(QMap<QString, QTreeWidgetItem*>& list, const QString& parent, QDomNode account)
{
bool rc = true;
while (rc == true && !account.isNull()) {
if (account.isElement()) {
QDomElement accountElement = account.toElement();
if (accountElement.tagName() == "account") {
QString name = QString("%1:%2").arg(parent).arg(accountElement.attribute("name"));
list[name] = 0;
hierarchy(list, name, account.firstChild());
}
}
account = account.nextSibling();
}
return rc;
}
void MyMoneyTemplate::hierarchy(QMap<QString, QTreeWidgetItem*>& list)
{
bool rc = !m_accounts.isNull();
QDomNode accounts = m_accounts;
while (rc == true && !accounts.isNull() && accounts.isElement()) {
QDomElement rootNode = accounts.toElement();
QString name = rootNode.attribute("name");
if (rootNode.tagName() == "account") {
rootNode = rootNode.firstChild().toElement();
- MyMoneyAccount::accountTypeE type = static_cast<MyMoneyAccount::accountTypeE>(accounts.toElement().attribute("type").toUInt());
+ eMyMoney::Account type = static_cast<eMyMoney::Account>(accounts.toElement().attribute("type").toUInt());
switch (type) {
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
- case MyMoneyAccount::Equity:
+ case eMyMoney::Account::Asset:
+ case eMyMoney::Account::Liability:
+ case eMyMoney::Account::Income:
+ case eMyMoney::Account::Expense:
+ case eMyMoney::Account::Equity:
if (name.isEmpty())
name = MyMoneyAccount::accountTypeToString(type);
list[name] = 0;
rc = hierarchy(list, name, rootNode);
break;
default:
rc = false;
break;
}
} else {
rc = false;
}
accounts = accounts.nextSibling();
}
}
bool MyMoneyTemplate::importTemplate(void(*callback)(int, int, const QString&))
{
m_progressCallback = callback;
bool rc = !m_accounts.isNull();
MyMoneyFile* file = MyMoneyFile::instance();
signalProgress(0, m_doc.elementsByTagName("account").count(), i18n("Loading template %1", m_source.url()));
m_accountsRead = 0;
while (rc == true && !m_accounts.isNull() && m_accounts.isElement()) {
QDomElement childElement = m_accounts.toElement();
if (childElement.tagName() == "account") {
++m_accountsRead;
MyMoneyAccount parent;
switch (childElement.attribute("type").toUInt()) {
- case MyMoneyAccount::Asset:
+ case (uint)eMyMoney::Account::Asset:
parent = file->asset();
break;
- case MyMoneyAccount::Liability:
+ case (uint)eMyMoney::Account::Liability:
parent = file->liability();
break;
- case MyMoneyAccount::Income:
+ case (uint)eMyMoney::Account::Income:
parent = file->income();
break;
- case MyMoneyAccount::Expense:
+ case (uint)eMyMoney::Account::Expense:
parent = file->expense();
break;
- case MyMoneyAccount::Equity:
+ case (uint)eMyMoney::Account::Equity:
parent = file->equity();
break;
default:
KMessageBox::error(KMyMoneyUtils::mainWindow(), i18n("<p>Invalid top-level account type <b>%1</b> in template file <b>%2</b></p>", childElement.attribute("type"), m_source.toDisplayString()));
rc = false;
}
if (rc == true) {
if (childElement.attribute("name").isEmpty())
rc = createAccounts(parent, childElement.firstChild());
else
rc = createAccounts(parent, childElement);
}
} else {
rc = false;
}
m_accounts = m_accounts.nextSibling();
}
signalProgress(-1, -1);
return rc;
}
bool MyMoneyTemplate::createAccounts(MyMoneyAccount& parent, QDomNode account)
{
bool rc = true;
while (rc == true && !account.isNull()) {
MyMoneyAccount acc;
if (account.isElement()) {
QDomElement accountElement = account.toElement();
if (accountElement.tagName() == "account") {
signalProgress(++m_accountsRead, 0);
QList<MyMoneyAccount> subAccountList;
QList<MyMoneyAccount>::ConstIterator it;
it = subAccountList.constEnd();
if (!parent.accountList().isEmpty()) {
MyMoneyFile::instance()->accountList(subAccountList, parent.accountList());
for (it = subAccountList.constBegin(); it != subAccountList.constEnd(); ++it) {
if ((*it).name() == accountElement.attribute("name")) {
acc = *it;
break;
}
}
}
if (it == subAccountList.constEnd()) {
// not found, we need to create it
acc.setName(accountElement.attribute("name"));
- acc.setAccountType(static_cast<MyMoneyAccount::_accountTypeE>(accountElement.attribute("type").toUInt()));
+ acc.setAccountType(static_cast<eMyMoney::Account>(accountElement.attribute("type").toUInt()));
setFlags(acc, account.firstChild());
try {
MyMoneyFile::instance()->addAccount(acc, parent);
} catch (const MyMoneyException &) {
}
}
createAccounts(acc, account.firstChild());
}
}
account = account.nextSibling();
}
return rc;
}
bool MyMoneyTemplate::setFlags(MyMoneyAccount& acc, QDomNode flags)
{
bool rc = true;
while (rc == true && !flags.isNull()) {
if (flags.isElement()) {
QDomElement flagElement = flags.toElement();
if (flagElement.tagName() == "flag") {
// make sure, we only store flags we know!
QString value = flagElement.attribute("name");
if (value == "Tax") {
acc.setValue(value.toLatin1(), "Yes");
} else if (value == "OpeningBalanceAccount") {
acc.setValue("OpeningBalanceAccount", "Yes");
} else {
KMessageBox::error(KMyMoneyUtils::mainWindow(), i18n("<p>Invalid flag type <b>%1</b> for account <b>%3</b> in template file <b>%2</b></p>", flagElement.attribute("name"), m_source.toDisplayString(), acc.name()));
rc = false;
}
QString currency = flagElement.attribute("currency");
if (!currency.isEmpty())
acc.setCurrencyId(currency);
}
}
flags = flags.nextSibling();
}
return rc;
}
void MyMoneyTemplate::signalProgress(int current, int total, const QString& msg)
{
if (m_progressCallback != 0)
(*m_progressCallback)(current, total, msg);
}
bool MyMoneyTemplate::exportTemplate(void(*callback)(int, int, const QString&))
{
m_progressCallback = callback;
m_doc = QDomDocument("KMYMONEY-TEMPLATE");
QDomProcessingInstruction instruct = m_doc.createProcessingInstruction(QString("xml"), QString("version=\"1.0\" encoding=\"utf-8\""));
m_doc.appendChild(instruct);
QDomElement mainElement = m_doc.createElement("kmymoney-account-template");
m_doc.appendChild(mainElement);
QDomElement title = m_doc.createElement("title");
QDomText t = m_doc.createTextNode(m_title);
title.appendChild(t);
mainElement.appendChild(title);
QDomElement shortDesc = m_doc.createElement("shortdesc");
t = m_doc.createTextNode(m_shortDesc);
shortDesc.appendChild(t);
mainElement.appendChild(shortDesc);
QDomElement longDesc = m_doc.createElement("longdesc");
t = m_doc.createTextNode(m_longDesc);
longDesc.appendChild(t);
mainElement.appendChild(longDesc);
QDomElement accounts = m_doc.createElement("accounts");
mainElement.appendChild(accounts);
addAccountStructure(accounts, MyMoneyFile::instance()->asset());
addAccountStructure(accounts, MyMoneyFile::instance()->expense());
addAccountStructure(accounts, MyMoneyFile::instance()->income());
addAccountStructure(accounts, MyMoneyFile::instance()->liability());
addAccountStructure(accounts, MyMoneyFile::instance()->equity());
return true;
}
const QString& MyMoneyTemplate::title() const
{
return m_title;
}
const QString& MyMoneyTemplate::shortDescription() const
{
return m_shortDesc;
}
const QString& MyMoneyTemplate::longDescription() const
{
return m_longDesc;
}
void MyMoneyTemplate::setTitle(const QString &s)
{
m_title = s;
}
void MyMoneyTemplate::setShortDescription(const QString &s)
{
m_shortDesc = s;
}
void MyMoneyTemplate::setLongDescription(const QString &s)
{
m_longDesc = s;
}
static bool nameLessThan(MyMoneyAccount &a1, MyMoneyAccount &a2)
{
return a1.name() < a2.name();
}
bool MyMoneyTemplate::addAccountStructure(QDomElement& parent, const MyMoneyAccount& acc)
{
QDomElement account = m_doc.createElement("account");
parent.appendChild(account);
if (MyMoneyFile::instance()->isStandardAccount(acc.id()))
account.setAttribute(QString("name"), QString());
else
account.setAttribute(QString("name"), acc.name());
- account.setAttribute(QString("type"), acc.accountType());
+ account.setAttribute(QString("type"), (int)acc.accountType());
// FIXME: add tax flag stuff
if (acc.pairs().contains("OpeningBalanceAccount")) {
QString openingBalanceAccount = acc.value("OpeningBalanceAccount");
if (openingBalanceAccount == "Yes") {
QDomElement flag = m_doc.createElement("flag");
flag.setAttribute(QString("name"), "OpeningBalanceAccount");
flag.setAttribute(QString("currency"), acc.currencyId());
account.appendChild(flag);
}
}
// any child accounts?
if (acc.accountList().count() > 0) {
QList<MyMoneyAccount> list;
MyMoneyFile::instance()->accountList(list, acc.accountList(), false);
qSort(list.begin(), list.end(), nameLessThan);
QList<MyMoneyAccount>::Iterator it;
for (it = list.begin(); it != list.end(); ++it) {
addAccountStructure(account, *it);
}
}
return true;
}
bool MyMoneyTemplate::saveTemplate(const QUrl &url)
{
QString filename;
if (!url.isValid()) {
qDebug("Invalid template URL '%s'", qPrintable(url.url()));
return false;
}
if (url.isLocalFile()) {
filename = url.toLocalFile();
QSaveFile qfile(filename/*, 0600*/);
if (qfile.open(QIODevice::WriteOnly)) {
saveToLocalFile(&qfile);
if (!qfile.commit()) {
throw MYMONEYEXCEPTION(i18n("Unable to write changes to '%1'", filename));
}
} else {
throw MYMONEYEXCEPTION(i18n("Unable to write changes to '%1'", filename));
}
} else {
QTemporaryFile tmpfile;
QSaveFile qfile(tmpfile.fileName());
if (qfile.open(QIODevice::WriteOnly)) {
saveToLocalFile(&qfile);
if (!qfile.commit()) {
throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'", url.url()));
}
} else {
throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'", url.url()));
}
// TODO: port to kf5
//if (!KIO::NetAccess::upload(tmpfile.fileName(), url, 0))
// throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'", url.url()));
}
return true;
}
bool MyMoneyTemplate::saveToLocalFile(QSaveFile* qfile)
{
QTextStream stream(qfile);
stream.setCodec("UTF-8");
stream << m_doc.toString();
stream.flush();
return true;
}
diff --git a/kmymoney/converter/tests/converter-test.cpp b/kmymoney/converter/tests/converter-test.cpp
index 0f1faea79..f3e0f0504 100644
--- a/kmymoney/converter/tests/converter-test.cpp
+++ b/kmymoney/converter/tests/converter-test.cpp
@@ -1,198 +1,198 @@
/***************************************************************************
convertertest.cpp
-------------------
copyright : (C) 2002 by Thomas Baumgart
email : ipwizard@users.sourceforge.net
Ace Jones <ace.j@hotpop.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 "converter-test.h"
#include <QtTest/QtTest>
#include <QFile>
// uses helper functions from reports tests
#include "reportstestcommon.h"
using namespace test;
#include "mymoneysecurity.h"
#include "mymoneyprice.h"
#include "mymoneyreport.h"
#include "mymoneystatement.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;
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", MyMoneyAccount::Checkings, moConverterCheckingOpen, QDate(2004, 5, 15), acAsset);
- acCredit = makeAccount("Credit Card", MyMoneyAccount::CreditCard, moConverterCreditOpen, QDate(2004, 7, 15), acLiability);
- acSolo = makeAccount("Solo", MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
- acParent = makeAccount("Parent", MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
- acChild = makeAccount("Child", MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
- acForeign = makeAccount("Foreign", MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+ acChecking = makeAccount("Checking Account", eMyMoney::Account::Checkings, moConverterCheckingOpen, QDate(2004, 5, 15), acAsset);
+ acCredit = makeAccount("Credit Card", eMyMoney::Account::CreditCard, moConverterCreditOpen, QDate(2004, 7, 15), acLiability);
+ acSolo = makeAccount("Solo", eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+ acParent = makeAccount("Parent", eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+ acChild = makeAccount("Child", eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
+ acForeign = makeAccount("Foreign", eMyMoney::Account::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<QString>("symbol");
QTest::addColumn<QString>("testname");
QTest::addColumn<QString>("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/matchfinder-test.cpp b/kmymoney/converter/tests/matchfinder-test.cpp
index cbe71e3a6..776bc3161 100644
--- a/kmymoney/converter/tests/matchfinder-test.cpp
+++ b/kmymoney/converter/tests/matchfinder-test.cpp
@@ -1,481 +1,481 @@
/***************************************************************************
KMyMoney transaction importing module - tests for ExistingTransactionMatchFinder
copyright : (C) 2012 by Lukasz Maszczynski <lukasz@maszczynski.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 "matchfinder-test.h"
#include "existingtransactionmatchfinder.h"
#include <QTest>
#include "mymoneyfile.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);
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(MyMoneyAccount::Expense);
+ account->setAccountType(eMyMoney::Account::Expense);
account->setOpeningDate(QDate(2012, 12, 01));
account->setCurrencyId(MyMoneyFile::instance()->baseCurrency().id());
otherAccount->setName("Some other account");
- otherAccount->setAccountType(MyMoneyAccount::Expense);
+ otherAccount->setAccountType(eMyMoney::Account::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", MyMoneySchedule::TYPE_TRANSFER, MyMoneySchedule::OCCUR_MONTHLY, 1, MyMoneySchedule::STYPE_BANKTRANSFER, tomorrow, tomorrow.addMonths(2), false, false);
+ 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/transactionmatchfinder.cpp b/kmymoney/converter/transactionmatchfinder.cpp
index 7fb4eafc0..164d49b43 100644
--- a/kmymoney/converter/transactionmatchfinder.cpp
+++ b/kmymoney/converter/transactionmatchfinder.cpp
@@ -1,155 +1,156 @@
/***************************************************************************
KMyMoney transaction importing module - base class for searching for a matching transaction
copyright : (C) 2012 by Lukasz Maszczynski <lukasz@maszczynski.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 "transactionmatchfinder.h"
#include <QDebug>
#include <KLocalizedString>
#include "mymoneyaccount.h"
#include "mymoneyfile.h"
+#include "mymoneypayee.h"
TransactionMatchFinder::TransactionMatchFinder(int _matchWindow) :
matchWindow(_matchWindow),
matchResult(MatchNotFound)
{
}
TransactionMatchFinder::~TransactionMatchFinder()
{
}
TransactionMatchFinder::MatchResult TransactionMatchFinder::findMatch(const MyMoneyTransaction& transactionToMatch, const MyMoneySplit& splitToMatch)
{
importedTransaction = transactionToMatch;
importedSplit = splitToMatch;
matchResult = MatchNotFound;
matchedTransaction.reset();
matchedSplit.reset();
matchedSchedule.reset();
QString date = importedTransaction.postDate().toString(Qt::ISODate);
QString payeeName = MyMoneyFile::instance()->payee(importedSplit.payeeId()).name();
QString amount = importedSplit.shares().formatMoney("", 2);
QString account = MyMoneyFile::instance()->account(importedSplit.accountId()).name();
qDebug() << "Looking for a match with transaction: " << date << "," << payeeName << "," << amount
<< "(referenced account: " << account << ")";
createListOfMatchCandidates();
findMatchInMatchCandidatesList();
return matchResult;
}
MyMoneySplit TransactionMatchFinder::getMatchedSplit() const
{
if (matchedSplit.isNull()) {
throw MYMONEYEXCEPTION(i18n("Internal error - no matching splits"));
}
return *matchedSplit;
}
MyMoneyTransaction TransactionMatchFinder::getMatchedTransaction() const
{
if (matchedTransaction.isNull()) {
throw MYMONEYEXCEPTION(i18n("Internal error - no matching transactions"));
}
return *matchedTransaction;
}
MyMoneySchedule TransactionMatchFinder::getMatchedSchedule() const
{
if (matchedSchedule.isNull()) {
throw MYMONEYEXCEPTION(i18n("Internal error - no matching schedules"));
}
return *matchedSchedule;
}
bool TransactionMatchFinder::splitsAreDuplicates(const MyMoneySplit& split1, const MyMoneySplit& split2, int amountVariation) const
{
return (splitsAmountsMatch(split1, split2, amountVariation) && splitsBankIdsDuplicated(split1, split2));
}
bool TransactionMatchFinder::splitsMatch(const MyMoneySplit& importedSplit, const MyMoneySplit& existingSplit, int amountVariation) const
{
return (splitsAccountsMatch(importedSplit, existingSplit) &&
splitsBankIdsMatch(importedSplit, existingSplit) &&
splitsAmountsMatch(importedSplit, existingSplit, amountVariation) &&
splitsPayeesMatchOrEmpty(importedSplit, existingSplit) &&
!existingSplit.isMatched());
}
bool TransactionMatchFinder::splitsAccountsMatch(const MyMoneySplit & split1, const MyMoneySplit & split2) const
{
return split1.accountId() == split2.accountId();
}
bool TransactionMatchFinder::splitsAmountsMatch(const MyMoneySplit& split1, const MyMoneySplit& split2, int amountVariation) const
{
MyMoneyMoney upper(split1.shares());
MyMoneyMoney lower(upper);
if ((amountVariation > 0) && (amountVariation < 100)) {
lower = lower - (lower.abs() * MyMoneyMoney(amountVariation, 100));
upper = upper + (upper.abs() * MyMoneyMoney(amountVariation, 100));
}
return (split2.shares() >= lower) && (split2.shares() <= upper);
}
bool TransactionMatchFinder::splitsBankIdsDuplicated(const MyMoneySplit& split1, const MyMoneySplit& split2) const
{
return (!split1.bankID().isEmpty()) && (split1.bankID() == split2.bankID());
}
bool TransactionMatchFinder::splitsBankIdsMatch(const MyMoneySplit& importedSplit, const MyMoneySplit& existingSplit) const
{
return (existingSplit.bankID().isEmpty() || existingSplit.bankID() == importedSplit.bankID());
}
bool TransactionMatchFinder::splitsPayeesMatchOrEmpty(const MyMoneySplit& split1, const MyMoneySplit& split2) const
{
bool payeesMatch = (split1.payeeId() == split2.payeeId());
bool atLeastOnePayeeIsNotSet = (split1.payeeId().isEmpty() || split2.payeeId().isEmpty());
return payeesMatch || atLeastOnePayeeIsNotSet;
}
void TransactionMatchFinder::findMatchingSplit(const MyMoneyTransaction& transaction, int amountVariation)
{
foreach (const MyMoneySplit & split, transaction.splits()) {
if (splitsAreDuplicates(importedSplit, split, amountVariation)) {
matchedTransaction.reset(new MyMoneyTransaction(transaction));
matchedSplit.reset(new MyMoneySplit(split));
matchResult = MatchDuplicate;
break;
}
if (splitsMatch(importedSplit, split, amountVariation)) {
matchedTransaction.reset(new MyMoneyTransaction(transaction));
matchedSplit.reset(new MyMoneySplit(split));
bool datesMatchPrecisely = importedTransaction.postDate() == transaction.postDate();
if (datesMatchPrecisely) {
matchResult = MatchPrecise;
} else {
matchResult = MatchImprecise;
}
break;
}
}
}
diff --git a/kmymoney/converter/webpricequote.cpp b/kmymoney/converter/webpricequote.cpp
index 9d7eef9e2..33c100623 100644
--- a/kmymoney/converter/webpricequote.cpp
+++ b/kmymoney/converter/webpricequote.cpp
@@ -1,1281 +1,1282 @@
/***************************************************************************
webpricequote.cpp
-------------------
begin : Thu Dec 30 2004
copyright : (C) 2004 by Ace Jones
email : Ace Jones <acejones@users.sourceforge.net>
copyright : (C) 2017 by Łukasz Wojniłowicz
email : Łukasz Wojniłowicz <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 "webpricequote.h"
// ----------------------------------------------------------------------------
// QT Headers
#include <QFile>
#include <QTextCodec>
#include <QByteArray>
#include <QString>
#include <QTemporaryFile>
#include <QUrl>
#include <QRegularExpression>
#include <QDebug>
#include <QLoggingCategory>
#include <QLocale>
// ----------------------------------------------------------------------------
// KDE Headers
#include <KLocalizedString>
#include <KConfig>
#include <KShell>
#include <KConfigGroup>
#include <KEncodingProber>
#include <KIO/Scheduler>
#include <KIO/Job>
#include <KJobWidgets>
// ----------------------------------------------------------------------------
// Project Headers
#include "mymoneyexception.h"
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
Q_DECLARE_LOGGING_CATEGORY(WEBPRICEQUOTE)
Q_LOGGING_CATEGORY(WEBPRICEQUOTE, "kmymoney_webpricequote")
// define static members
QString WebPriceQuote::m_financeQuoteScriptPath;
QStringList WebPriceQuote::m_financeQuoteSources;
class WebPriceQuote::Private
{
public:
WebPriceQuoteProcess m_filter;
QString m_quoteData;
QString m_webID;
QString m_kmmID;
QDate m_date;
QDate m_fromDate;
QDate m_toDate;
double m_price;
WebPriceQuoteSource m_source;
PricesProfile m_CSVSource;
};
WebPriceQuote::WebPriceQuote(QObject* _parent):
QObject(_parent),
d(new Private)
{
// only do this once (I know, it is not thread safe, but it should
// always yield the same result so we don't do any semaphore foo here)
if (m_financeQuoteScriptPath.isEmpty()) {
m_financeQuoteScriptPath = QStandardPaths::locate(QStandardPaths::DataLocation, QString("misc/financequote.pl"));
}
connect(&d->m_filter, SIGNAL(processExited(QString)), this, SLOT(slotParseQuote(QString)));
}
WebPriceQuote::~WebPriceQuote()
{
delete d;
}
void WebPriceQuote::setDate(const QDate& _from, const QDate& _to)
{
d->m_fromDate = _from;
d->m_toDate = _to;
}
bool WebPriceQuote::launch(const QString& _webID, const QString& _kmmID, const QString& _sourcename)
{
if (_sourcename.contains("Finance::Quote"))
return (launchFinanceQuote(_webID, _kmmID, _sourcename));
else if ((!d->m_fromDate.isValid() || !d->m_toDate.isValid()) ||
(d->m_fromDate == d->m_toDate && d->m_toDate == QDate::currentDate()))
return (launchNative(_webID, _kmmID, _sourcename));
else
return launchCSV(_webID, _kmmID, _sourcename);
}
bool WebPriceQuote::launchCSV(const QString& _webID, const QString& _kmmID, const QString& _sourcename)
{
d->m_webID = _webID;
d->m_kmmID = _kmmID;
// emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id));
// Get sources from the config file
QString sourcename = _sourcename;
if (sourcename.isEmpty())
return false;
if (sourcename == QLatin1String("Yahoo Currency"))
sourcename = QLatin1String("Stooq Currency");
if (quoteSources().contains(sourcename))
d->m_source = WebPriceQuoteSource(sourcename);
else {
emit error(i18n("Source <placeholder>%1</placeholder> does not exist.", sourcename));
emit failed(d->m_kmmID, d->m_webID);
return false;
}
int monthOffset = 0;
if (sourcename.contains(QLatin1String("Yahoo"), Qt::CaseInsensitive))
monthOffset = -1;
QUrl url;
QString urlStr = d->m_source.m_csvUrl;
int i = urlStr.indexOf(QLatin1String("%y"));
if (i != -1)
urlStr.replace(i, 2, QString().setNum(d->m_fromDate.year()));
i = urlStr.indexOf(QLatin1String("%y"));
if (i != -1)
urlStr.replace(i, 2, QString().setNum(d->m_toDate.year()));
i = urlStr.indexOf(QLatin1String("%m"));
if (i != -1)
urlStr.replace(i, 2, QString().setNum(d->m_fromDate.month() + monthOffset).rightJustified(2, QLatin1Char('0')));
i = urlStr.indexOf(QLatin1String("%m"));
if (i != -1)
urlStr.replace(i, 2, QString().setNum(d->m_toDate.month() + monthOffset).rightJustified(2, QLatin1Char('0')));
i = urlStr.indexOf(QLatin1String("%d"));
if (i != -1)
urlStr.replace(i, 2, QString().setNum(d->m_fromDate.day()).rightJustified(2, QLatin1Char('0')));
i = urlStr.indexOf(QLatin1String("%d"));
if (i != -1)
urlStr.replace(i, 2, QString().setNum(d->m_toDate.day()).rightJustified(2, QLatin1Char('0')));
if (urlStr.contains(QLatin1String("%y")) || urlStr.contains(QLatin1String("%m")) || urlStr.contains(QLatin1String("%d"))) {
emit error(i18n("Cannot resolve input date."));
emit failed(d->m_kmmID, d->m_webID);
return false;
}
bool isCurrency = false;
if (urlStr.contains(QLatin1String("%2"))) {
d->m_CSVSource.m_profileType = Profile::CurrencyPrices;
isCurrency = true;
} else
d->m_CSVSource.m_profileType = Profile::StockPrices;
d->m_CSVSource.m_profileName = sourcename;
if (!d->m_CSVSource.readSettings(CSVImporter::configFile())) {
QMap<QString, PricesProfile> result = defaultCSVQuoteSources();
d->m_CSVSource = result.value(sourcename);
if (d->m_CSVSource.m_profileName.isEmpty()) {
emit error(i18n("CSV source <placeholder>%1</placeholder> does not exist.", sourcename));
emit failed(d->m_kmmID, d->m_webID);
return false;
}
}
if (isCurrency) {
// this is a two-symbol quote. split the symbol into two. valid symbol
// characters are: 0-9, A-Z and the dot. anything else is a separator
QRegularExpression splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", QRegularExpression::CaseInsensitiveOption);
QRegularExpressionMatch match;
// if we've truly found 2 symbols delimited this way...
if (d->m_webID.indexOf(splitrx, 0, &match) != -1) {
url = QUrl(urlStr.arg(match.captured(1), match.captured(2)));
d->m_CSVSource.m_currencySymbol = match.captured(2);
d->m_CSVSource.m_securitySymbol = match.captured(1);
} else {
qCDebug(WEBPRICEQUOTE) << "WebPriceQuote::launch() did not find 2 symbols";
emit error(i18n("Cannot find from and to currency."));
emit failed(d->m_kmmID, d->m_webID);
return false;
}
} else {
// a regular one-symbol quote
url = QUrl(urlStr.arg(d->m_webID));
d->m_CSVSource.m_securityName = MyMoneyFile::instance()->security(d->m_kmmID).name();
d->m_CSVSource.m_securitySymbol = MyMoneyFile::instance()->security(d->m_kmmID).tradingSymbol();
}
if (url.isLocalFile()) {
emit error(i18n("Local quote sources aren't supported."));
emit failed(d->m_kmmID, d->m_webID);
return false;
} else {
//silent download
emit status(i18n("Fetching URL %1...", url.toDisplayString()));
QString tmpFile;
{
QTemporaryFile tmpFileFile;
tmpFileFile.setAutoRemove(false);
if (tmpFileFile.open())
qDebug() << "created tmpfile";
tmpFile = tmpFileFile.fileName();
}
QFile::remove(tmpFile);
const QUrl dest = QUrl::fromLocalFile(tmpFile);
KIO::Scheduler::checkSlaveOnHold(true);
KIO::Job *job = KIO::file_copy(url, dest, -1, KIO::HideProgressInfo);
connect(job, SIGNAL(result(KJob*)),
this, SLOT(downloadCSV(KJob*)));
}
return true;
}
bool WebPriceQuote::launchNative(const QString& _webID, const QString& _kmmID, const QString& _sourcename)
{
d->m_webID = _webID;
d->m_kmmID = _kmmID;
if (_webID == i18n("[No identifier]")) {
emit error(i18n("<placeholder>%1</placeholder> skipped because it doesn't have identification number.", _kmmID));
emit failed(d->m_kmmID, d->m_webID);
return false;
}
// emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id));
// Get sources from the config file
QString sourcename = _sourcename;
if (sourcename.isEmpty())
sourcename = "Yahoo";
if (quoteSources().contains(sourcename))
d->m_source = WebPriceQuoteSource(sourcename);
else {
emit error(i18n("Source <placeholder>%1</placeholder> does not exist.", sourcename));
emit failed(d->m_kmmID, d->m_webID);
return false;
}
QUrl url;
// if the source has room for TWO symbols..
if (d->m_source.m_url.contains("%2")) {
// this is a two-symbol quote. split the symbol into two. valid symbol
// characters are: 0-9, A-Z and the dot. anything else is a separator
QRegularExpression splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", QRegularExpression::CaseInsensitiveOption);
QRegularExpressionMatch match;
// if we've truly found 2 symbols delimited this way...
if (d->m_webID.indexOf(splitrx, 0, &match) != -1) {
url = QUrl(d->m_source.m_url.arg(match.captured(1), match.captured(2)));
} else {
qCDebug(WEBPRICEQUOTE) << "WebPriceQuote::launch() did not find 2 symbols";
}
} else {
// a regular one-symbol quote
url = QUrl(d->m_source.m_url.arg(d->m_webID));
}
if (url.isLocalFile()) {
emit status(i18nc("The process x is executing", "Executing %1...", url.toLocalFile()));
QString program;
QStringList arguments = url.toLocalFile().split(' ', QString::SkipEmptyParts);
if (!arguments.isEmpty()) {
program = arguments.first();
arguments.removeFirst();
}
d->m_filter.setWebID(d->m_webID);
d->m_filter.setProcessChannelMode(QProcess::MergedChannels);
d->m_filter.start(program, arguments);
if (!d->m_filter.waitForStarted()) {
emit error(i18n("Unable to launch: %1", url.toLocalFile()));
slotParseQuote(QString());
}
} else {
//silent download
emit status(i18n("Fetching URL %1...", url.toDisplayString()));
QString tmpFile;
{
QTemporaryFile tmpFileFile;
tmpFileFile.setAutoRemove(false);
if (tmpFileFile.open())
qDebug() << "created tmpfile";
tmpFile = tmpFileFile.fileName();
}
QFile::remove(tmpFile);
const QUrl dest = QUrl::fromLocalFile(tmpFile);
KIO::Scheduler::checkSlaveOnHold(true);
KIO::Job *job = KIO::file_copy(url, dest, -1, KIO::HideProgressInfo);
connect(job, SIGNAL(result(KJob*)),
this, SLOT(downloadResult(KJob*)));
}
return true;
}
void WebPriceQuote::downloadCSV(KJob* job)
{
QString tmpFile = dynamic_cast<KIO::FileCopyJob*>(job)->destUrl().toLocalFile();
QUrl url = dynamic_cast<KIO::FileCopyJob*>(job)->srcUrl();
if (!job->error())
{
qDebug() << "Downloaded" << tmpFile << "from" << url;
QFile f(tmpFile);
if (f.open(QIODevice::ReadOnly)) {
f.close();
slotParseCSVQuote(tmpFile);
} else {
emit error(i18n("Failed to open downloaded file"));
slotParseCSVQuote(QString());
}
} else {
emit error(job->errorString());
slotParseCSVQuote(QString());
}
}
void WebPriceQuote::downloadResult(KJob* job)
{
QString tmpFile = dynamic_cast<KIO::FileCopyJob*>(job)->destUrl().toLocalFile();
QUrl url = dynamic_cast<KIO::FileCopyJob*>(job)->srcUrl();
if (!job->error())
{
qDebug() << "Downloaded" << tmpFile << "from" << url;
QFile f(tmpFile);
if (f.open(QIODevice::ReadOnly)) {
// Find out the page encoding and convert it to unicode
QByteArray page = f.readAll();
KEncodingProber prober(KEncodingProber::Universal);
prober.feed(page);
QTextCodec* codec = QTextCodec::codecForName(prober.encoding());
if (!codec)
codec = QTextCodec::codecForLocale();
QString quote = codec->toUnicode(page);
f.close();
slotParseQuote(quote);
} else {
emit error(i18n("Failed to open downloaded file"));
slotParseQuote(QString());
}
QFile::remove(tmpFile);
} else {
emit error(job->errorString());
slotParseQuote(QString());
}
}
bool WebPriceQuote::launchFinanceQuote(const QString& _webID, const QString& _kmmID,
const QString& _sourcename)
{
bool result = true;
d->m_webID = _webID;
d->m_kmmID = _kmmID;
QString FQSource = _sourcename.section(' ', 1);
d->m_source = WebPriceQuoteSource(_sourcename, m_financeQuoteScriptPath, m_financeQuoteScriptPath,
"\"([^,\"]*)\",.*", // webIDRegExp
WebPriceQuoteSource::identifyBy::Symbol,
"[^,]*,[^,]*,\"([^\"]*)\"", // price regexp
"[^,]*,([^,]*),.*", // date regexp
"%y-%m-%d"); // date format
//emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id));
QStringList arguments;
arguments << m_financeQuoteScriptPath << FQSource << KShell::quoteArg(_webID);
d->m_filter.setWebID(d->m_webID);
emit status(i18nc("Executing 'script' 'online source' 'investment symbol' ", "Executing %1 %2 %3...", m_financeQuoteScriptPath, FQSource, _webID));
d->m_filter.setProcessChannelMode(QProcess::MergedChannels);
d->m_filter.start(QLatin1Literal("perl"), arguments);
// This seems to work best if we just block until done.
if (d->m_filter.waitForFinished()) {
result = true;
} else {
emit error(i18n("Unable to launch: %1", m_financeQuoteScriptPath));
slotParseQuote(QString());
}
return result;
}
void WebPriceQuote::slotParseCSVQuote(const QString& filename)
{
bool isOK = true;
if (filename.isEmpty())
isOK = false;
else {
MyMoneyStatement st;
CSVImporter* csvImporter = new CSVImporter;
st = csvImporter->unattendedImport(filename, &d->m_CSVSource);
if (!st.m_listPrices.isEmpty())
emit csvquote(d->m_kmmID, d->m_webID, st);
else
isOK = false;
delete csvImporter;
QFile::remove(filename);
}
if (!isOK) {
emit error(i18n("Unable to update price for %1", d->m_webID));
emit failed(d->m_kmmID, d->m_webID);
}
}
void WebPriceQuote::slotParseQuote(const QString& _quotedata)
{
QString quotedata = _quotedata;
d->m_quoteData = quotedata;
bool gotprice = false;
bool gotdate = false;
qCDebug(WEBPRICEQUOTE) << "quotedata" << _quotedata;
if (! quotedata.isEmpty()) {
if (!d->m_source.m_skipStripping) {
// First, remove extranous non-data elements
// HTML tags
quotedata.remove(QRegularExpression("<[^>]*>"));
// &...;'s
quotedata.replace(QRegularExpression("&\\w+;"), QLatin1String(" "));
// Extra white space
quotedata = quotedata.simplified();
qCDebug(WEBPRICEQUOTE) << "stripped text" << quotedata;
}
QRegularExpression webIDRegExp(d->m_source.m_webID);
QRegularExpression dateRegExp(d->m_source.m_date);
QRegularExpression priceRegExp(d->m_source.m_price);
QRegularExpressionMatch match;
if (quotedata.indexOf(webIDRegExp, 0, &match) > -1) {
qCDebug(WEBPRICEQUOTE) << "Identifier" << match.captured(1);
emit status(i18n("Identifier found: '%1'", match.captured(1)));
}
if (quotedata.indexOf(priceRegExp, 0, &match) > -1) {
gotprice = true;
// Deal with european quotes that come back as X.XXX,XX or XX,XXX
//
// We will make the assumption that ALL prices have a decimal separator.
// So "1,000" always means 1.0, not 1000.0.
//
// Remove all non-digits from the price string except the last one, and
// set the last one to a period.
QString pricestr = match.captured(1);
int pos = pricestr.lastIndexOf(QRegularExpression("\\D"));
if (pos > 0) {
pricestr[pos] = QLatin1Char('.');
pos = pricestr.lastIndexOf(QRegularExpression("\\D"), pos - 1);
}
while (pos > 0) {
pricestr.remove(pos, 1);
pos = pricestr.lastIndexOf(QRegularExpression("\\D"), pos);
}
d->m_price = pricestr.toDouble();
qCDebug(WEBPRICEQUOTE) << "Price" << pricestr;
emit status(i18n("Price found: '%1' (%2)", pricestr, d->m_price));
}
if (quotedata.indexOf(dateRegExp, 0, &match) > -1) {
QString datestr = match.captured(1);
MyMoneyDateFormat dateparse(d->m_source.m_dateformat);
try {
d->m_date = dateparse.convertString(datestr, false /*strict*/);
gotdate = true;
qCDebug(WEBPRICEQUOTE) << "Date" << datestr;
emit status(i18n("Date found: '%1'", d->m_date.toString()));;
} catch (const MyMoneyException &) {
// emit error(i18n("Unable to parse date %1 using format %2: %3").arg(datestr,dateparse.format(),e.what()));
d->m_date = QDate::currentDate();
gotdate = true;
}
}
if (gotprice && gotdate) {
emit quote(d->m_kmmID, d->m_webID, d->m_date, d->m_price);
} else {
emit error(i18n("Unable to update price for %1 (no price or no date)", d->m_webID));
emit failed(d->m_kmmID, d->m_webID);
}
} else {
emit error(i18n("Unable to update price for %1 (empty quote data)", d->m_webID));
emit failed(d->m_kmmID, d->m_webID);
}
}
const QMap<QString, PricesProfile> WebPriceQuote::defaultCSVQuoteSources()
{
QMap<QString, PricesProfile> result;
// tip: possible delimiter indexes are in csvenums.h
result[QLatin1String("Stooq")] = PricesProfile(QLatin1String("Stooq"),
106, 1, 0, DateFormat::YearMonthDay, FieldDelimiter::Semicolon,
TextDelimiter::DoubleQuote, DecimalSymbol::Dot,
QMap<Column, int>{{Column::Date, 0}, {Column::Price, 4}},
2, Profile::StockPrices);
result[QLatin1String("Stooq Currency")] = PricesProfile(QLatin1String("Stooq Currency"),
106, 1, 0, DateFormat::YearMonthDay, FieldDelimiter::Semicolon,
TextDelimiter::DoubleQuote, DecimalSymbol::Dot,
QMap<Column, int>{{Column::Date, 0}, {Column::Price, 4}},
2, Profile::CurrencyPrices);
result[QLatin1String("Yahoo")] = PricesProfile(QLatin1String("Yahoo"),
106, 1, 0, DateFormat::YearMonthDay, FieldDelimiter::Comma,
TextDelimiter::DoubleQuote, DecimalSymbol::Dot,
QMap<Column, int>{{Column::Date, 0}, {Column::Price, 4}},
2, Profile::StockPrices);
result[QLatin1String("Nasdaq Baltic - Shares")] = PricesProfile(QLatin1String("Nasdaq Baltic - Shares"),
106, 1, 0, DateFormat::DayMonthYear, FieldDelimiter::Tab,
TextDelimiter::DoubleQuote, DecimalSymbol::Dot,
QMap<Column, int>{{Column::Date, 0}, {Column::Price, 5}},
2, Profile::StockPrices);
result[QLatin1String("Nasdaq Baltic - Funds")] = PricesProfile(QLatin1String("Nasdaq Baltic - Funds"),
106, 1, 0, DateFormat::DayMonthYear, FieldDelimiter::Tab,
TextDelimiter::DoubleQuote, DecimalSymbol::Dot,
QMap<Column, int>{{Column::Date, 0}, {Column::Price, 5}},
2, Profile::StockPrices);
return result;
}
const QMap<QString, WebPriceQuoteSource> WebPriceQuote::defaultQuoteSources()
{
QMap<QString, WebPriceQuoteSource> result;
result["Yahoo"] = WebPriceQuoteSource("Yahoo",
"http://finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d1",
"http://ichart.finance.yahoo.com/table.csv?s=%1&a=%m&b=%d&c=%y&d=%m&e=%d&f=%y&g=d&ignore=.csv",
"\"([^,\"]*)\",.*", // webIDRegExp
WebPriceQuoteSource::identifyBy::Symbol,
"[^,]*,([^,]*),.*", // priceregexp
"[^,]*,[^,]*,\"([^\"]*)\"", // dateregexp
"%m %d %y" // dateformat
);
result["Yahoo Currency"] = WebPriceQuoteSource("Yahoo Currency",
"http://finance.yahoo.com/d/quotes.csv?s=%1%2=X&f=sl1d1",
"",
"\"([^,\"]*)\",.*", // webIDRegExp
WebPriceQuoteSource::identifyBy::Symbol,
"[^,]*,([^,]*),.*", // priceregexp
"[^,]*,[^,]*,\"([^\"]*)\"", // dateregexp
"%m %d %y" // dateformat
);
// 2009-08-20 Yahoo UK has no quotes and has comma separators
// sl1d1 format for Yahoo UK doesn't seem to give a date ever
// sl1d3 gives US locale time (9:99pm) and date (mm/dd/yyyy)
result["Yahoo UK"] = WebPriceQuoteSource("Yahoo UK",
"http://uk.finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d3",
"",
"^([^,]*),.*", // webIDRegExp
WebPriceQuoteSource::identifyBy::Symbol,
"^[^,]*,([^,]*),.*", // priceregexp
"^[^,]*,[^,]*, [^ ]* (../../....).*", // dateregexp
"%m/%d/%y" // dateformat
);
// sl1d1 format for Yahoo France doesn't seem to give a date ever
// sl1d3 gives us time (99h99) and date
result["Yahoo France"] = WebPriceQuoteSource("Yahoo France",
"http://fr.finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d3",
"",
"([^;]*).*", // webIDRegExp
WebPriceQuoteSource::identifyBy::Symbol,
"[^;]*.([^;]*),*", // priceregexp
"[^;]*.[^;]*...h...([^;]*)", // dateregexp
"%d/%m/%y" // dateformat
);
// This quote source provided by Danny Scott
result["Yahoo Canada"] = WebPriceQuoteSource("Yahoo Canada",
"http://ca.finance.yahoo.com/q?s=%1",
"",
"%1", // webIDRegExp
WebPriceQuoteSource::identifyBy::Symbol,
"Last Trade: (\\d+\\.\\d+)", // price regexp
"day, (.\\D+\\d+\\D+\\d+)", // date regexp
"%m %d %y" // date format
);
// Update on 2017-06 by Łukasz Wojniłowicz
result["Globe & Mail"] = WebPriceQuoteSource("Globe & Mail",
"http://globefunddb.theglobeandmail.com/gishome/plsql/gis.price_history?pi_fund_id=%1",
QString(),
QString(), // webIDRegExp
WebPriceQuoteSource::identifyBy::IdentificationNumber,
"Fund Price:\\D+(\\d+\\.\\d+)", // priceregexp
"Fund Price:.+as at (\\w+ \\d+, \\d+)\\)", // dateregexp
"%m %d %y" // dateformat
);
// Update on 2017-06 by Łukasz Wojniłowicz
result["MSN"] = WebPriceQuoteSource("MSN",
"http://www.msn.com/en-us/money/stockdetails/%1",
QString(),
QString(), // webIDRegExp
WebPriceQuoteSource::identifyBy::Symbol,
"(\\d+\\.\\d+) [+-]\\d+.\\d+", // priceregexp
"(\\d+/\\d+/\\d+)", //dateregexp
"%y %m %d" // dateformat
);
// Finanztreff (replaces VWD.DE) supplied by Michael Zimmerman
result["Finanztreff"] = WebPriceQuoteSource("Finanztreff",
"http://finanztreff.de/kurse_einzelkurs_detail.htn?u=100&i=%1",
"",
QString(), // webIDRegExp
WebPriceQuoteSource::identifyBy::IdentificationNumber,
"([0-9]+,\\d+).+Gattung:Fonds", // priceregexp
"\\).(\\d+\\D+\\d+\\D+\\d+)", // dateregexp (doesn't work; date in chart
"%d.%m.%y" // dateformat
);
// First revision by Michael Zimmerman
// Update on 2017-06 by Łukasz Wojniłowicz
result["boerseonlineaktien"] = WebPriceQuoteSource("Börse Online - Aktien",
"http://www.boerse-online.de/aktie/%1-Aktie",
QString(),
QString(), // webIDRegExp
WebPriceQuoteSource::identifyBy::Name,
"Aktienkurs\\D+(\\d+,\\d{2})", // priceregexp
"Datum (\\d{2}\\.\\d{2}\\.\\d{4})", // dateregexp
"%d.%m.%y" // dateformat
);
// This quote source provided by e-mail and should replace the previous one:
// From: David Houlden <djhoulden@gmail.com>
// To: kmymoney@kde.org
// Date: Sat, 6 Apr 2013 13:22:45 +0100
// Updated on 2017-06 by Łukasz Wojniłowicz
result["Financial Times - UK Funds"] = WebPriceQuoteSource("Financial Times",
"http://funds.ft.com/uk/Tearsheet/Summary?s=%1",
QString(),
QString(), // webIDRegExp
WebPriceQuoteSource::identifyBy::IdentificationNumber,
"Price\\D+([\\d,]*\\d+\\.\\d+)", // price regexp
"Data delayed at least 15 minutes, as of\\ (.*)\\.", // date regexp
"%m %d %y", // date format
true // skip HTML stripping
);
// The following two price sources were contributed by
// Marc Zahnlecker <tf2k@users.sourceforge.net>
result["Wallstreet-Online.DE (Default)"] = WebPriceQuoteSource("Wallstreet-Online.DE (Default)",
"http://www.wallstreet-online.de/si/?k=%1&spid=ws",
"",
"Symbol:(\\w+)", // webIDRegExp
WebPriceQuoteSource::identifyBy::Symbol,
"Letzter Kurs: ([0-9.]+,\\d+)", // priceregexp
", (\\d+\\D+\\d+\\D+\\d+)", // dateregexp
"%d %m %y" // dateformat
);
// (tf2k) The "mpid" is I think the market place id. In this case five
// stands for Hamburg.
//
// Here the id for several market places: 2 Frankfurt, 3 Berlin, 4
// Düsseldorf, 5 Hamburg, 6 München/Munich, 7 Hannover, 9 Stuttgart, 10
// Xetra, 32 NASDAQ, 36 NYSE
result["Wallstreet-Online.DE (Hamburg)"] = WebPriceQuoteSource("Wallstreet-Online.DE (Hamburg)",
"http://fonds.wallstreet-online.de/si/?k=%1&spid=ws&mpid=5",
"",
"Symbol:(\\w+)", // webIDRegExp
WebPriceQuoteSource::identifyBy::Symbol,
"Fonds \\(EUR\\) ([0-9.]+,\\d+)", // priceregexp
", (\\d+\\D+\\d+\\D+\\d+)", // dateregexp
"%d %m %y" // dateformat
);
// First revision on 2017-06 by Łukasz Wojniłowicz
result["Puls Biznesu"] = WebPriceQuoteSource("Puls Biznesu",
"http://notowania.pb.pl/instrument/%1",
QString(),
QString(), // webIDRegExp
WebPriceQuoteSource::identifyBy::IdentificationNumber,
"(\\d+,\\d{2})\\D+\\d+,\\d{2}%", // price regexp
"(\\d{4}-\\d{2}-\\d{2}) \\d{2}:\\d{2}:\\d{2}", // date regexp
"%y %m %d" // date format
);
// The following price quote was contributed by
// Piotr Adacha <piotr.adacha@googlemail.com>
// I would like to post new Online Query Settings for KMyMoney. This set is
// suitable to query stooq.com service, providing quotes for stocks, futures,
// mutual funds and other financial instruments from Polish Gielda Papierow
// Wartosciowych (GPW). Unfortunately, none of well-known international
// services provide quotes for this market (biggest one in central and eastern
// Europe), thus, I think it could be helpful for Polish users of KMyMoney (and
// I am one of them for almost a year).
// Update on 2017-06 by Łukasz Wojniłowicz
result["Stooq"] = WebPriceQuoteSource("Stooq",
"http://stooq.com/q/?s=%1",
"http://stooq.pl/q/d/l/?s=%1&d1=%y%m%d&d2=%y%m%d&i=d&c=1",
QString(), // webIDRegExp
WebPriceQuoteSource::identifyBy::Symbol,
"Last(\\d+\\.\\d+).*Date", // price regexp
"(\\d{4,4}-\\d{2,2}-\\d{2,2})", // date regexp
"%y %m %d" // date format
);
// First revision on 2017-06 by Łukasz Wojniłowicz
result[QLatin1String("Stooq Currency")] = WebPriceQuoteSource("Stooq Currency",
"http://stooq.com/q/?s=%1%2",
"http://stooq.pl/q/d/l/?s=%1%2&d1=%y%m%d&d2=%y%m%d&i=d&c=1",
QString(), // webIDRegExp
WebPriceQuoteSource::identifyBy::Symbol,
"Last.*(\\d+\\.\\d+).*Date", // price regexp
"(\\d{4,4}-\\d{2,2}-\\d{2,2})", // date regexp
"%y %m %d" // date format
);
// First revision on 2017-06 by Łukasz Wojniłowicz
result["Nasdaq Baltic - Shares"] = WebPriceQuoteSource("Nasdaq Baltic - Shares",
"http://www.nasdaqbaltic.com/market/?pg=details&instrument=%1&lang=en",
"http://www.nasdaqbaltic.com/market/?instrument=%1&pg=details&tab=historical&lang=en&date=&start=%d.%m.%y&end=%d.%m.%y&pg=details&pg2=equity&downloadcsv=1&csv_style=english",
QString(), // webIDRegExp
WebPriceQuoteSource::identifyBy::IdentificationNumber,
"lastPrice\\D+(\\d+,\\d+)", // priceregexp
"as of: (\\d{2}.\\d{2}.\\d{4})", // dateregexp
"%d.%m.%y", // dateformat
true
);
// First revision on 2017-06 by Łukasz Wojniłowicz
result["Nasdaq Baltic - Funds"] = WebPriceQuoteSource("Nasdaq Baltic - Funds",
"http://www.nasdaqbaltic.com/market/?pg=details&instrument=%1&lang=en",
"http://www.nasdaqbaltic.com/market/?instrument=%1&pg=details&tab=historical&lang=en&date=&start=%d.%m.%y&end=%d.%m.%y&pg=details&pg2=equity&downloadcsv=1&csv_style=english",
QString(), // webIDRegExp
WebPriceQuoteSource::identifyBy::IdentificationNumber,
"marketShareDetailTable(.+\\n){21}\\D+(\\d+)", // priceregexp
"as of: (\\d{2}.\\d{2}.\\d{4})", // dateregexp
"%d.%m.%y", // dateformat
true
);
return result;
}
const QStringList WebPriceQuote::quoteSources(const _quoteSystemE _system)
{
if (_system == Native)
return (quoteSourcesNative());
else
return (quoteSourcesFinanceQuote());
}
const QStringList WebPriceQuote::quoteSourcesNative()
{
KSharedConfigPtr kconfig = KSharedConfig::openConfig();
QStringList groups = kconfig->groupList();
QStringList::Iterator it;
QRegularExpression onlineQuoteSource(QString("^Online-Quote-Source-(.*)$"));
QRegularExpressionMatch match;
// get rid of all 'non online quote source' entries
for (it = groups.begin(); it != groups.end(); it = groups.erase(it)) {
if ((*it).indexOf(onlineQuoteSource, 0, &match) >= 0) {
// Insert the name part
it = groups.insert(it, match.captured(1));
++it;
}
}
// if the user has the OLD quote source defined, now is the
// time to remove that entry and convert it to the new system.
if (! groups.count() && kconfig->hasGroup("Online Quotes Options")) {
KConfigGroup grp = kconfig->group("Online Quotes Options");
QString url(grp.readEntry("URL", "http://finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d1"));
QString webIDRegExp(grp.readEntry("SymbolRegex", "\"([^,\"]*)\",.*"));
QString priceRegExp(grp.readEntry("PriceRegex", "[^,]*,([^,]*),.*"));
QString dateRegExp(grp.readEntry("DateRegex", "[^,]*,[^,]*,\"([^\"]*)\""));
kconfig->deleteGroup("Online Quotes Options");
groups += "Old Source";
grp = kconfig->group(QString(QLatin1String("Online-Quote-Source-%1")).arg("Old Source"));
grp.writeEntry("URL", url);
grp.writeEntry("CSVURL", "http://finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d1");
grp.writeEntry("IDRegex", webIDRegExp);
grp.writeEntry("PriceRegex", priceRegExp);
grp.writeEntry("DateRegex", dateRegExp);
grp.writeEntry("DateFormatRegex", "%m %d %y");
grp.sync();
}
// if the user has OLD quote source based only on symbols (and not ISIN)
// now is the time to convert it to the new system.
foreach (const auto group, groups) {
KConfigGroup grp = kconfig->group(QString(QLatin1String("Online-Quote-Source-%1")).arg(group));
if (grp.hasKey("SymbolRegex")) {
grp.writeEntry("IDRegex", grp.readEntry("SymbolRegex"));
grp.deleteEntry("SymbolRegex");
} else
break;
}
// Set up each of the default sources. These are done piecemeal so that
// when we add a new source, it's automatically picked up. And any changes
// are also picked up.
QMap<QString, WebPriceQuoteSource> defaults = defaultQuoteSources();
QMap<QString, WebPriceQuoteSource>::const_iterator it_source = defaults.constBegin();
while (it_source != defaults.constEnd()) {
if (! groups.contains((*it_source).m_name)) {
groups += (*it_source).m_name;
(*it_source).write();
kconfig->sync();
}
++it_source;
}
return groups;
}
const QStringList WebPriceQuote::quoteSourcesFinanceQuote()
{
if (m_financeQuoteSources.empty()) { // run the process one time only
// since this is a static function it can be called without constructing an object
// so we need to make sure that m_financeQuoteScriptPath is properly initialized
if (m_financeQuoteScriptPath.isEmpty()) {
m_financeQuoteScriptPath = QStandardPaths::locate(QStandardPaths::DataLocation, QString("misc/financequote.pl"));
}
FinanceQuoteProcess getList;
getList.launch(m_financeQuoteScriptPath);
while (!getList.isFinished()) {
QCoreApplication::processEvents();
}
m_financeQuoteSources = getList.getSourceList();
}
return (m_financeQuoteSources);
}
//
// Helper class to load/save an individual source
//
WebPriceQuoteSource::WebPriceQuoteSource(const QString& name, const QString& url, const QString &csvUrl, const QString& id, const identifyBy idBy, const QString& price, const QString& date, const QString& dateformat, bool skipStripping):
m_name(name),
m_url(url),
m_csvUrl(csvUrl),
m_webID(id),
m_webIDBy(idBy),
m_price(price),
m_date(date),
m_dateformat(dateformat),
m_skipStripping(skipStripping)
{ }
WebPriceQuoteSource::WebPriceQuoteSource(const QString& name)
{
m_name = name;
KSharedConfigPtr kconfig = KSharedConfig::openConfig();
KConfigGroup grp = kconfig->group(QString("Online-Quote-Source-%1").arg(m_name));
m_webID = grp.readEntry("IDRegex");
m_webIDBy = static_cast<WebPriceQuoteSource::identifyBy>(grp.readEntry("IDBy", "0").toInt());
m_date = grp.readEntry("DateRegex");
m_dateformat = grp.readEntry("DateFormatRegex", "%m %d %y");
m_price = grp.readEntry("PriceRegex");
m_url = grp.readEntry("URL");
m_csvUrl = grp.readEntry("CSVURL");
m_skipStripping = grp.readEntry("SkipStripping", false);
}
void WebPriceQuoteSource::write() const
{
KSharedConfigPtr kconfig = KSharedConfig::openConfig();
KConfigGroup grp = kconfig->group(QString("Online-Quote-Source-%1").arg(m_name));
grp.writeEntry("URL", m_url);
grp.writeEntry("CSVURL", m_csvUrl);
grp.writeEntry("PriceRegex", m_price);
grp.writeEntry("DateRegex", m_date);
grp.writeEntry("DateFormatRegex", m_dateformat);
grp.writeEntry("IDRegex", m_webID);
grp.writeEntry("IDBy", static_cast<int>(m_webIDBy));
if (m_skipStripping)
grp.writeEntry("SkipStripping", m_skipStripping);
else
grp.deleteEntry("SkipStripping");
kconfig->sync();
}
void WebPriceQuoteSource::rename(const QString& name)
{
remove();
m_name = name;
write();
}
void WebPriceQuoteSource::remove() const
{
KSharedConfigPtr kconfig = KSharedConfig::openConfig();
kconfig->deleteGroup(QString("Online-Quote-Source-%1").arg(m_name));
kconfig->sync();
}
//
// Helper class to babysit the KProcess used for running the local script in that case
//
WebPriceQuoteProcess::WebPriceQuoteProcess()
{
connect(this, SIGNAL(readyReadStandardOutput()), this, SLOT(slotReceivedDataFromFilter()));
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessExited(int,QProcess::ExitStatus)));
}
void WebPriceQuoteProcess::slotReceivedDataFromFilter()
{
// qDebug() << "WebPriceQuoteProcess::slotReceivedDataFromFilter(): " << QString(data);
m_string += QString(readAllStandardOutput());
}
void WebPriceQuoteProcess::slotProcessExited(int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/)
{
// qDebug() << "WebPriceQuoteProcess::slotProcessExited()";
emit processExited(m_string);
m_string.truncate(0);
}
//
// Helper class to babysit the KProcess used for running the Finance Quote sources script
//
FinanceQuoteProcess::FinanceQuoteProcess()
{
m_isDone = false;
m_string = "";
m_fqNames["aex"] = "AEX";
m_fqNames["aex_futures"] = "AEX Futures";
m_fqNames["aex_options"] = "AEX Options";
m_fqNames["amfiindia"] = "AMFI India";
m_fqNames["asegr"] = "ASE";
m_fqNames["asia"] = "Asia (Yahoo, ...)";
m_fqNames["asx"] = "ASX";
m_fqNames["australia"] = "Australia (ASX, Yahoo, ...)";
m_fqNames["bmonesbittburns"] = "BMO NesbittBurns";
m_fqNames["brasil"] = "Brasil (Yahoo, ...)";
m_fqNames["canada"] = "Canada (Yahoo, ...)";
m_fqNames["canadamutual"] = "Canada Mutual (Fund Library, ...)";
m_fqNames["deka"] = "Deka Investments";
m_fqNames["dutch"] = "Dutch (AEX, ...)";
m_fqNames["dwsfunds"] = "DWS";
m_fqNames["europe"] = "Europe (Yahoo, ...)";
m_fqNames["fidelity"] = "Fidelity (Fidelity, ...)";
m_fqNames["fidelity_direct"] = "Fidelity Direct";
m_fqNames["financecanada"] = "Finance Canada";
m_fqNames["ftportfolios"] = "First Trust (First Trust, ...)";
m_fqNames["ftportfolios_direct"] = "First Trust Portfolios";
m_fqNames["fundlibrary"] = "Fund Library";
m_fqNames["greece"] = "Greece (ASE, ...)";
m_fqNames["indiamutual"] = "India Mutual (AMFI, ...)";
m_fqNames["maninv"] = "Man Investments";
m_fqNames["fool"] = "Motley Fool";
m_fqNames["nasdaq"] = "Nasdaq (Yahoo, ...)";
m_fqNames["nz"] = "New Zealand (Yahoo, ...)";
m_fqNames["nyse"] = "NYSE (Yahoo, ...)";
m_fqNames["nzx"] = "NZX";
m_fqNames["platinum"] = "Platinum Asset Management";
m_fqNames["seb_funds"] = "SEB";
m_fqNames["sharenet"] = "Sharenet";
m_fqNames["za"] = "South Africa (Sharenet, ...)";
m_fqNames["troweprice_direct"] = "T. Rowe Price";
m_fqNames["troweprice"] = "T. Rowe Price";
m_fqNames["tdefunds"] = "TD Efunds";
m_fqNames["tdwaterhouse"] = "TD Waterhouse Canada";
m_fqNames["tiaacref"] = "TIAA-CREF";
m_fqNames["trustnet"] = "Trustnet";
m_fqNames["uk_unit_trusts"] = "U.K. Unit Trusts";
m_fqNames["unionfunds"] = "Union Investments";
m_fqNames["tsp"] = "US Govt. Thrift Savings Plan";
m_fqNames["usfedbonds"] = "US Treasury Bonds";
m_fqNames["usa"] = "USA (Yahoo, Fool ...)";
m_fqNames["vanguard"] = "Vanguard";
m_fqNames["vwd"] = "VWD";
m_fqNames["yahoo"] = "Yahoo";
m_fqNames["yahoo_asia"] = "Yahoo Asia";
m_fqNames["yahoo_australia"] = "Yahoo Australia";
m_fqNames["yahoo_brasil"] = "Yahoo Brasil";
m_fqNames["yahoo_europe"] = "Yahoo Europe";
m_fqNames["yahoo_nz"] = "Yahoo New Zealand";
m_fqNames["zifunds"] = "Zuerich Investments";
connect(this, SIGNAL(readyReadStandardOutput()), this, SLOT(slotReceivedDataFromFilter()));
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessExited()));
connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(slotProcessExited()));
}
void FinanceQuoteProcess::slotReceivedDataFromFilter()
{
QByteArray data(readAllStandardOutput());
// qDebug() << "WebPriceQuoteProcess::slotReceivedDataFromFilter(): " << QString(data);
m_string += QString(data);
}
void FinanceQuoteProcess::slotProcessExited()
{
// qDebug() << "WebPriceQuoteProcess::slotProcessExited()";
m_isDone = true;
}
void FinanceQuoteProcess::launch(const QString& scriptPath)
{
QStringList arguments;
arguments << scriptPath << QLatin1Literal("-l");
setProcessChannelMode(QProcess::ForwardedOutputChannel);
start(QLatin1Literal("perl"), arguments);
if (! waitForStarted()) qWarning("Unable to start FQ script");
return;
}
const QStringList FinanceQuoteProcess::getSourceList() const
{
QStringList raw = m_string.split(0x0A, QString::SkipEmptyParts);
QStringList sources;
QStringList::iterator it;
for (it = raw.begin(); it != raw.end(); ++it) {
if (m_fqNames[*it].isEmpty()) sources.append(*it);
else sources.append(m_fqNames[*it]);
}
sources.sort();
return (sources);
}
const QString FinanceQuoteProcess::crypticName(const QString& niceName) const
{
QString ret(niceName);
fqNameMap::const_iterator it;
for (it = m_fqNames.begin(); it != m_fqNames.end(); ++it) {
if (niceName == it.value()) {
ret = it.key();
break;
}
}
return (ret);
}
const QString FinanceQuoteProcess::niceName(const QString& crypticName) const
{
QString ret(m_fqNames[crypticName]);
if (ret.isEmpty()) ret = crypticName;
return (ret);
}
//
// Universal date converter
//
// In 'strict' mode, this is designed to be compatable with the QIF profile date
// converter. However, that converter deals with the concept of an apostrophe
// format in a way I don't understand. So for the moment, they are 99%
// compatable, waiting on that issue. (acejones)
const QDate MyMoneyDateFormat::convertString(const QString& _in, bool _strict, unsigned _centurymidpoint) const
{
//
// Break date format string into component parts
//
QRegularExpression formatrex("%([mdy]+)(\\W+)%([mdy]+)(\\W+)%([mdy]+)", QRegularExpression::CaseInsensitiveOption);
QRegularExpressionMatch match;
if (m_format.indexOf(formatrex, 0, &match) == -1) {
throw MYMONEYEXCEPTION("Invalid format string");
}
QStringList formatParts;
formatParts += match.captured(1);
formatParts += match.captured(3);
formatParts += match.captured(5);
QStringList formatDelimiters;
formatDelimiters += match.captured(2);
formatDelimiters += match.captured(4);
match = QRegularExpressionMatch();
//
// Break input string up into component parts,
// using the delimiters found in the format string
//
QRegularExpression inputrex;
inputrex.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
// strict mode means we must enforce the delimiters as specified in the
// format. non-strict allows any delimiters
if (_strict)
inputrex.setPattern(QString("(\\w+)%1(\\w+)%2(\\w+)").arg(formatDelimiters[0], formatDelimiters[1]));
else
inputrex.setPattern("(\\w+)\\W+(\\w+)\\W+(\\w+)");
if (_in.indexOf(inputrex, 0, &match) == -1) {
throw MYMONEYEXCEPTION("Invalid input string");
}
QStringList scannedParts;
scannedParts += match.captured(1).toLower();
scannedParts += match.captured(2).toLower();
scannedParts += match.captured(3).toLower();
match = QRegularExpressionMatch();
//
// Convert the scanned parts into actual date components
//
unsigned day = 0, month = 0, year = 0;
bool ok;
QRegularExpression digitrex("(\\d+)");
QStringList::const_iterator it_scanned = scannedParts.constBegin();
QStringList::const_iterator it_format = formatParts.constBegin();
while (it_scanned != scannedParts.constEnd()) {
// decide upon the first character of the part
switch ((*it_format).at(0).cell()) {
case 'd':
// remove any extraneous non-digits (e.g. read "3rd" as 3)
ok = false;
if ((*it_scanned).indexOf(digitrex, 0, &match) != -1)
day = match.captured(1).toUInt(&ok);
if (!ok || day > 31)
throw MYMONEYEXCEPTION(QString("Invalid day entry: %1").arg(*it_scanned));
break;
case 'm':
month = (*it_scanned).toUInt(&ok);
if (!ok) {
month = 0;
// maybe it's a textual date
unsigned i = 1;
// search the name in the current selected locale
QLocale locale;
while (i <= 12) {
if (locale.standaloneMonthName(i).toLower() == *it_scanned
|| locale.standaloneMonthName(i, QLocale::ShortFormat).toLower() == *it_scanned) {
month = i;
break;
}
++i;
}
// in case we did not find the month in the current locale,
// we look for it in the C locale
if(month == 0) {
QLocale localeC(QLocale::C);
if( !(locale == localeC)) {
i = 1;
while (i <= 12) {
if (localeC.standaloneMonthName(i).toLower() == *it_scanned
|| localeC.standaloneMonthName(i, QLocale::ShortFormat).toLower() == *it_scanned) {
month = i;
break;
}
++i;
}
}
}
}
if (month < 1 || month > 12)
throw MYMONEYEXCEPTION(QString("Invalid month entry: %1").arg(*it_scanned));
break;
case 'y':
if (_strict && (*it_scanned).length() != (*it_format).length())
throw MYMONEYEXCEPTION(QString("Length of year (%1) does not match expected length (%2).")
.arg(*it_scanned, *it_format));
year = (*it_scanned).toUInt(&ok);
if (!ok)
throw MYMONEYEXCEPTION(QString("Invalid year entry: %1").arg(*it_scanned));
//
// 2-digit year case
//
// this algorithm will pick a year within +/- 50 years of the
// centurymidpoint parameter. i.e. if the midpoint is 2000,
// then 0-49 will become 2000-2049, and 50-99 will become 1950-1999
if (year < 100) {
unsigned centuryend = _centurymidpoint + 50;
unsigned centurybegin = _centurymidpoint - 50;
if (year < centuryend % 100)
year += 100;
year += centurybegin - centurybegin % 100;
}
if (year < 1900)
throw MYMONEYEXCEPTION(QString("Invalid year (%1)").arg(year));
break;
default:
throw MYMONEYEXCEPTION("Invalid format character");
}
++it_scanned;
++it_format;
}
QDate result(year, month, day);
if (! result.isValid())
throw MYMONEYEXCEPTION(QString("Invalid date (yr%1 mo%2 dy%3)").arg(year).arg(month).arg(day));
return result;
}
//
// Unit test helpers
//
convertertest::QuoteReceiver::QuoteReceiver(WebPriceQuote* q, QObject* parent) :
QObject(parent)
{
connect(q, SIGNAL(quote(QString,QString,QDate,double)),
this, SLOT(slotGetQuote(QString,QString,QDate,double)));
connect(q, SIGNAL(status(QString)),
this, SLOT(slotStatus(QString)));
connect(q, SIGNAL(error(QString)),
this, SLOT(slotError(QString)));
}
convertertest::QuoteReceiver::~QuoteReceiver()
{
}
void convertertest::QuoteReceiver::slotGetQuote(const QString&, const QString&, const QDate& d, const double& m)
{
// qDebug() << "test::QuoteReceiver::slotGetQuote( , " << d << " , " << m.toString() << " )";
m_price = MyMoneyMoney(m);
m_date = d;
}
void convertertest::QuoteReceiver::slotStatus(const QString& msg)
{
// qDebug() << "test::QuoteReceiver::slotStatus( " << msg << " )";
m_statuses += msg;
}
void convertertest::QuoteReceiver::slotError(const QString& msg)
{
// qDebug() << "test::QuoteReceiver::slotError( " << msg << " )";
m_errors += msg;
}
// leave this moc until we will have resolved our dependency issues
// now 'converter' depends on 'kmymoney' a pointer to the application
// defined in main.cpp, which makes this static library unusable without
// the --as-needed linker flag;
// otherwise the 'moc' methods of this object will be linked into the automoc
// object file which contains methods from all the other objects from this
// library, thus even if the --as-needed option is used all objects will be
// pulled in while linking 'convertertest' which only needs the WebPriceQuote
// object - spent a whole day investigating this
#include "moc_webpricequote.cpp"
diff --git a/kmymoney/dialogs/investtransactioneditor.cpp b/kmymoney/dialogs/investtransactioneditor.cpp
index 2011ff752..5f60d28fa 100644
--- a/kmymoney/dialogs/investtransactioneditor.cpp
+++ b/kmymoney/dialogs/investtransactioneditor.cpp
@@ -1,1126 +1,1127 @@
/***************************************************************************
investtransactioneditor.cpp
----------
begin : Fri Dec 15 2006
copyright : (C) 2006 by Thomas Baumgart
email : Thomas Baumgart <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 "investtransactioneditor.h"
#include <typeinfo>
// ----------------------------------------------------------------------------
// QT Includes
#include <QLabel>
#include <QList>
#include <QPushButton>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KTextEdit>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneycategory.h"
#include "kmymoneydateinput.h"
#include "kmymoneyedit.h"
#include "kmymoneyaccountselector.h"
#include "kmymoneymvccombo.h"
#include "mymoneyfile.h"
+#include "mymoneyprice.h"
#include "ksplittransactiondlg.h"
#include "kcurrencycalculator.h"
#include "kmymoneyglobalsettings.h"
#include "investactivities.h"
#include "kmymoneyutils.h"
#include "kmymoneycompletion.h"
using namespace KMyMoneyRegister;
using namespace KMyMoneyTransactionForm;
using namespace Invest;
class InvestTransactionEditor::Private
{
friend class Invest::Activity;
public:
Private(InvestTransactionEditor* parent) :
m_parent(parent),
m_activity(0) {
m_phonyAccount = MyMoneyAccount("Phony-ID", MyMoneyAccount());
}
~Private() {
delete m_activity;
}
QWidget* haveWidget(const QString& name) {
return m_parent->haveWidget(name);
}
void hideCategory(const QString& name) {
if (KMyMoneyCategory* cat = dynamic_cast<KMyMoneyCategory*>(haveWidget(name))) {
cat->hide();
cat->splitButton()->hide();
}
}
InvestTransactionEditor* m_parent;
Activity* m_activity;
MyMoneyAccount m_phonyAccount;
MyMoneySplit m_phonySplit;
};
InvestTransactionEditor::InvestTransactionEditor() :
m_transactionType(MyMoneySplit::UnknownTransactionType),
d(new Private(this))
{
}
InvestTransactionEditor::~InvestTransactionEditor()
{
delete d;
}
InvestTransactionEditor::InvestTransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::InvestTransaction* item, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate) :
TransactionEditor(regForm, item, list, lastPostDate),
d(new Private(this))
{
// after the gometries of the container are updated hide the widgets which are not needed by the current activity
connect(m_regForm, SIGNAL(geometriesUpdated()), this, SLOT(slotTransactionContainerGeometriesUpdated()));
// dissect the transaction into its type, splits, currency, security etc.
KMyMoneyUtils::dissectTransaction(m_transaction, m_split,
m_assetAccountSplit,
m_feeSplits,
m_interestSplits,
m_security,
m_currency,
m_transactionType);
// determine initial activity object
activityFactory(m_transactionType);
}
void InvestTransactionEditor::activityFactory(MyMoneySplit::investTransactionTypeE type)
{
if (!d->m_activity || type != d->m_activity->type()) {
delete d->m_activity;
switch (type) {
default:
case MyMoneySplit::BuyShares:
d->m_activity = new Buy(this);
break;
case MyMoneySplit::SellShares:
d->m_activity = new Sell(this);
break;
case MyMoneySplit::Dividend:
case MyMoneySplit::Yield:
d->m_activity = new Div(this);
break;
case MyMoneySplit::ReinvestDividend:
d->m_activity = new Reinvest(this);
break;
case MyMoneySplit::AddShares:
d->m_activity = new Add(this);
break;
case MyMoneySplit::RemoveShares:
d->m_activity = new Remove(this);
break;
case MyMoneySplit::SplitShares:
d->m_activity = new Split(this);
break;
case MyMoneySplit::InterestIncome:
d->m_activity = new IntInc(this);
break;
}
}
}
void InvestTransactionEditor::createEditWidgets()
{
KMyMoneyActivityCombo* activity = new KMyMoneyActivityCombo();
m_editWidgets["activity"] = activity;
connect(activity, SIGNAL(activitySelected(MyMoneySplit::investTransactionTypeE)), this, SLOT(slotUpdateActivity(MyMoneySplit::investTransactionTypeE)));
connect(activity, SIGNAL(activitySelected(MyMoneySplit::investTransactionTypeE)), this, SLOT(slotUpdateButtonState()));
m_editWidgets["postdate"] = new kMyMoneyDateInput;
KMyMoneySecurity* security = new KMyMoneySecurity;
security->setPlaceholderText(i18n("Security"));
m_editWidgets["security"] = security;
connect(security, SIGNAL(itemSelected(QString)), this, SLOT(slotUpdateSecurity(QString)));
connect(security, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateButtonState()));
connect(security, SIGNAL(createItem(QString,QString&)), this, SLOT(slotCreateSecurity(QString,QString&)));
connect(security, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool)));
KMyMoneyCategory* asset = new KMyMoneyCategory(0, false);
asset->setPlaceholderText(i18n("Asset account"));
m_editWidgets["asset-account"] = asset;
connect(asset, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateButtonState()));
connect(asset, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool)));
KMyMoneyCategory* fees = new KMyMoneyCategory(0, true);
fees->setPlaceholderText(i18n("Fees"));
m_editWidgets["fee-account"] = fees;
connect(fees, SIGNAL(itemSelected(QString)), this, SLOT(slotUpdateFeeCategory(QString)));
connect(fees, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateButtonState()));
connect(fees, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateFeeVisibility(QString)));
connect(fees, SIGNAL(createItem(QString,QString&)), this, SLOT(slotCreateFeeCategory(QString,QString&)));
connect(fees, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool)));
connect(fees->splitButton(), SIGNAL(clicked()), this, SLOT(slotEditFeeSplits()));
KMyMoneyCategory* interest = new KMyMoneyCategory(0, true);
interest->setPlaceholderText(i18n("Interest"));
m_editWidgets["interest-account"] = interest;
connect(interest, SIGNAL(itemSelected(QString)), this, SLOT(slotUpdateInterestCategory(QString)));
connect(interest, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateButtonState()));
connect(interest, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateInterestVisibility(QString)));
connect(interest, SIGNAL(createItem(QString,QString&)), this, SLOT(slotCreateInterestCategory(QString,QString&)));
connect(interest, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool)));
connect(interest->splitButton(), SIGNAL(clicked()), this, SLOT(slotEditInterestSplits()));
KTagContainer* tag = new KTagContainer;
tag->tagCombo()->setPlaceholderText(i18n("Tag"));
tag->tagCombo()->setObjectName(QLatin1String("Tag"));
m_editWidgets["tag"] = tag;
connect(tag->tagCombo(), SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateButtonState()));
connect(tag->tagCombo(), SIGNAL(createItem(QString,QString&)), this, SIGNAL(createTag(QString,QString&)));
connect(tag->tagCombo(), SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool)));
KTextEdit* memo = new KTextEdit;
memo->setTabChangesFocus(true);
m_editWidgets["memo"] = memo;
connect(memo, SIGNAL(textChanged()), this, SLOT(slotUpdateInvestMemoState()));
connect(memo, SIGNAL(textChanged()), this, SLOT(slotUpdateButtonState()));
d->m_activity->m_memoText.clear();
d->m_activity->m_memoChanged = false;
kMyMoneyEdit* value = new kMyMoneyEdit;
value->setPlaceholderText(i18n("Shares"));
value->setResetButtonVisible(false);
m_editWidgets["shares"] = value;
connect(value, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButtonState()));
connect(value, SIGNAL(valueChanged(QString)), this, SLOT(slotUpdateTotalAmount()));
value = new kMyMoneyEdit;
value->setPlaceholderText(i18n("Price"));
value->setResetButtonVisible(false);
m_editWidgets["price"] = value;
connect(value, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButtonState()));
connect(value, SIGNAL(valueChanged(QString)), this, SLOT(slotUpdateTotalAmount()));
value = new kMyMoneyEdit;
// TODO once we have the selected transactions as array of Transaction
// we can allow multiple splits for fee and interest
value->setResetButtonVisible(false);
m_editWidgets["fee-amount"] = value;
connect(value, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButtonState()));
connect(value, SIGNAL(valueChanged(QString)), this, SLOT(slotUpdateTotalAmount()));
value = new kMyMoneyEdit;
// TODO once we have the selected transactions as array of Transaction
// we can allow multiple splits for fee and interest
value->setResetButtonVisible(false);
m_editWidgets["interest-amount"] = value;
connect(value, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButtonState()));
connect(value, SIGNAL(valueChanged(QString)), this, SLOT(slotUpdateTotalAmount()));
KMyMoneyReconcileCombo* reconcile = new KMyMoneyReconcileCombo;
m_editWidgets["status"] = reconcile;
connect(reconcile, SIGNAL(itemSelected(QString)), this, SLOT(slotUpdateButtonState()));
KMyMoneyRegister::QWidgetContainer::iterator it_w;
for (it_w = m_editWidgets.begin(); it_w != m_editWidgets.end(); ++it_w) {
(*it_w)->installEventFilter(this);
}
QLabel* label;
m_editWidgets["activity-label"] = label = new QLabel(i18n("Activity"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["postdate-label"] = label = new QLabel(i18n("Date"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["security-label"] = label = new QLabel(i18n("Security"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["shares-label"] = label = new QLabel(i18n("Shares"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["asset-label"] = label = new QLabel(i18n("Account"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["price-label"] = label = new QLabel(i18n("Price/share"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["fee-label"] = label = new QLabel(i18n("Fees"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["fee-amount-label"] = label = new QLabel("");
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["interest-label"] = label = new QLabel(i18n("Interest"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["interest-amount-label"] = label = new QLabel(i18n("Interest"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["memo-label"] = label = new QLabel(i18n("Memo"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["total"] = label = new QLabel("");
label->setAlignment(Qt::AlignVCenter | Qt::AlignRight);
m_editWidgets["total-label"] = label = new QLabel(i18nc("Total value", "Total"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["status-label"] = label = new QLabel(i18n("Status"));
label->setAlignment(Qt::AlignVCenter);
// if we don't have more than 1 selected transaction, we don't need
// the "don't change" item in some of the combo widgets
if (m_transactions.count() < 2) {
reconcile->removeDontCare();
}
}
int InvestTransactionEditor::slotEditFeeSplits()
{
return editSplits("fee-account", "fee-amount", m_feeSplits, false, SLOT(slotEditFeeSplits()));
}
int InvestTransactionEditor::slotEditInterestSplits()
{
return editSplits("interest-account", "interest-amount", m_interestSplits, true, SLOT(slotEditInterestSplits()));
}
int InvestTransactionEditor::editSplits(const QString& categoryWidgetName, const QString& amountWidgetName, QList<MyMoneySplit>& splits, bool isIncome, const char* slotEditSplits)
{
int rc = QDialog::Rejected;
if (!m_openEditSplits) {
// only get in here in a single instance
m_openEditSplits = true;
// force focus change to update all data
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets[categoryWidgetName]);
QWidget* w = category->splitButton();
if (w)
w->setFocus();
kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(haveWidget(amountWidgetName));
MyMoneyTransaction transaction;
transaction.setCommodity(m_currency.id());
if (splits.count() == 0 && !category->selectedItem().isEmpty()) {
MyMoneySplit s;
s.setAccountId(category->selectedItem());
s.setShares(amount->value());
s.setValue(s.shares());
splits << s;
}
// use the transactions commodity as the currency indicator for the splits
// this is used to allow some useful setting for the fractions in the amount fields
try {
d->m_phonyAccount.setCurrencyId(m_transaction.commodity());
d->m_phonyAccount.fraction(MyMoneyFile::instance()->security(m_transaction.commodity()));
} catch (const MyMoneyException &) {
qDebug("Unable to setup precision");
}
if (createPseudoTransaction(transaction, splits)) {
MyMoneyMoney value;
QPointer<KSplitTransactionDlg> dlg = new KSplitTransactionDlg(transaction,
d->m_phonySplit,
d->m_phonyAccount,
false,
isIncome,
MyMoneyMoney(),
m_priceInfo,
m_regForm);
// connect(dlg, SIGNAL(newCategory(MyMoneyAccount&)), this, SIGNAL(newCategory(MyMoneyAccount&)));
if ((rc = dlg->exec()) == QDialog::Accepted) {
transaction = dlg->transaction();
// collect splits out of the transaction
splits.clear();
QList<MyMoneySplit>::const_iterator it_s;
MyMoneyMoney fees;
for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) {
if ((*it_s).accountId() == d->m_phonyAccount.id())
continue;
splits << *it_s;
fees += (*it_s).shares();
}
if (isIncome)
fees = -fees;
QString categoryId;
setupCategoryWidget(category, splits, categoryId, slotEditSplits);
amount->setValue(fees);
slotUpdateTotalAmount();
}
delete dlg;
}
// focus jumps into the memo field
if ((w = haveWidget("memo")) != 0) {
w->setFocus();
}
m_openEditSplits = false;
}
return rc;
}
bool InvestTransactionEditor::createPseudoTransaction(MyMoneyTransaction& t, const QList<MyMoneySplit>& splits)
{
t.removeSplits();
MyMoneySplit split;
split.setAccountId(d->m_phonyAccount.id());
split.setValue(-subtotal(splits));
split.setShares(split.value());
t.addSplit(split);
d->m_phonySplit = split;
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = splits.begin(); it_s != splits.end(); ++it_s) {
split = *it_s;
split.clearId();
t.addSplit(split);
}
return true;
}
void InvestTransactionEditor::slotCreateSecurity(const QString& name, QString& id)
{
MyMoneyAccount acc;
QRegExp exp("([^:]+)");
if (exp.indexIn(name) != -1) {
acc.setName(exp.cap(1));
emit createSecurity(acc, m_account);
// return id
id = acc.id();
if (!id.isEmpty()) {
slotUpdateSecurity(id);
}
}
}
void InvestTransactionEditor::slotCreateFeeCategory(const QString& name, QString& id)
{
MyMoneyAccount acc;
acc.setName(name);
emit createCategory(acc, MyMoneyFile::instance()->expense());
// return id
id = acc.id();
}
void InvestTransactionEditor::slotUpdateFeeCategory(const QString& id)
{
haveWidget("fee-amount")->setDisabled(id.isEmpty());
}
void InvestTransactionEditor::slotUpdateFeeVisibility(const QString& txt)
{
static const QSet<MyMoneySplit::investTransactionTypeE> transactionTypesWithoutFee = QSet<MyMoneySplit::investTransactionTypeE>()
<< MyMoneySplit::AddShares << MyMoneySplit::RemoveShares << MyMoneySplit::SplitShares;
kMyMoneyEdit* feeAmount = dynamic_cast<kMyMoneyEdit*>(haveWidget("fee-amount"));
feeAmount->setHidden(txt.isEmpty());
QLabel* l = dynamic_cast<QLabel*>(haveWidget("fee-amount-label"));
KMyMoneyCategory* fee = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account"));
const bool hideFee = txt.isEmpty() || transactionTypesWithoutFee.contains(d->m_activity->type());
// no fee expected so hide
if (hideFee) {
if (l) {
l->setText("");
}
feeAmount->hide();
fee->splitButton()->hide();
} else {
if (l) {
l->setText(i18n("Fee Amount"));
}
feeAmount->show();
fee->splitButton()->show();
}
}
void InvestTransactionEditor::slotUpdateInterestCategory(const QString& id)
{
haveWidget("interest-amount")->setDisabled(id.isEmpty());
}
void InvestTransactionEditor::slotUpdateInterestVisibility(const QString& txt)
{
static const QSet<MyMoneySplit::investTransactionTypeE> transactionTypesWithInterest = QSet<MyMoneySplit::investTransactionTypeE>()
<< MyMoneySplit::BuyShares << MyMoneySplit::SellShares << MyMoneySplit::Dividend << MyMoneySplit::InterestIncome << MyMoneySplit::Yield;
QWidget* w = haveWidget("interest-amount");
w->setHidden(txt.isEmpty());
QLabel* l = dynamic_cast<QLabel*>(haveWidget("interest-amount-label"));
KMyMoneyCategory* interest = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account"));
const bool showInterest = !txt.isEmpty() && transactionTypesWithInterest.contains(d->m_activity->type());
if (interest && showInterest) {
interest->splitButton()->show();
w->show();
if (l)
l->setText(i18n("Interest"));
} else {
if (interest) {
interest->splitButton()->hide();
w->hide();
if (l)
l->setText(QString());
}
}
}
void InvestTransactionEditor::slotCreateInterestCategory(const QString& name, QString& id)
{
MyMoneyAccount acc;
acc.setName(name);
emit createCategory(acc, MyMoneyFile::instance()->income());
id = acc.id();
}
void InvestTransactionEditor::slotReloadEditWidgets()
{
KMyMoneyCategory* interest = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account"));
KMyMoneyCategory* fees = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account"));
KMyMoneySecurity* security = dynamic_cast<KMyMoneySecurity*>(haveWidget("security"));
AccountSet aSet;
QString id;
// interest-account
aSet.clear();
- aSet.addAccountGroup(MyMoneyAccount::Income);
+ aSet.addAccountGroup(eMyMoney::Account::Income);
aSet.load(interest->selector());
setupCategoryWidget(interest, m_interestSplits, id, SLOT(slotEditInterestSplits()));
// fee-account
aSet.clear();
- aSet.addAccountGroup(MyMoneyAccount::Expense);
+ aSet.addAccountGroup(eMyMoney::Account::Expense);
aSet.load(fees->selector());
setupCategoryWidget(fees, m_feeSplits, id, SLOT(slotEditFeeSplits()));
// security
aSet.clear();
aSet.load(security->selector(), i18n("Security"), m_account.accountList(), true);
}
void InvestTransactionEditor::loadEditWidgets(KMyMoneyRegister::Action /* action */)
{
QString id;
kMyMoneyDateInput* postDate = dynamic_cast<kMyMoneyDateInput*>(haveWidget("postdate"));
KMyMoneyReconcileCombo* reconcile = dynamic_cast<KMyMoneyReconcileCombo*>(haveWidget("status"));
KMyMoneySecurity* security = dynamic_cast<KMyMoneySecurity*>(haveWidget("security"));
KMyMoneyActivityCombo* activity = dynamic_cast<KMyMoneyActivityCombo*>(haveWidget("activity"));
KMyMoneyCategory* asset = dynamic_cast<KMyMoneyCategory*>(haveWidget("asset-account"));
KTextEdit* memo = dynamic_cast<KTextEdit*>(m_editWidgets["memo"]);
kMyMoneyEdit* value;
KMyMoneyCategory* interest = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account"));
KMyMoneyCategory* fees = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account"));
// check if the current transaction has a reference to an equity account
bool haveEquityAccount = false;
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = m_transaction.splits().constBegin(); !haveEquityAccount && it_s != m_transaction.splits().constEnd(); ++it_s) {
MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
- if (acc.accountType() == MyMoneyAccount::Equity)
+ if (acc.accountType() == eMyMoney::Account::Equity)
haveEquityAccount = true;
}
// asset-account
AccountSet aSet;
aSet.clear();
- aSet.addAccountType(MyMoneyAccount::Checkings);
- aSet.addAccountType(MyMoneyAccount::Savings);
- aSet.addAccountType(MyMoneyAccount::Cash);
- aSet.addAccountType(MyMoneyAccount::Asset);
- aSet.addAccountType(MyMoneyAccount::Currency);
- aSet.addAccountType(MyMoneyAccount::CreditCard);
+ aSet.addAccountType(eMyMoney::Account::Checkings);
+ aSet.addAccountType(eMyMoney::Account::Savings);
+ aSet.addAccountType(eMyMoney::Account::Cash);
+ aSet.addAccountType(eMyMoney::Account::Asset);
+ aSet.addAccountType(eMyMoney::Account::Currency);
+ aSet.addAccountType(eMyMoney::Account::CreditCard);
if (KMyMoneyGlobalSettings::expertMode() || haveEquityAccount)
- aSet.addAccountGroup(MyMoneyAccount::Equity);
+ aSet.addAccountGroup(eMyMoney::Account::Equity);
aSet.load(asset->selector());
// security
security->setSuppressObjectCreation(false); // allow object creation on the fly
aSet.clear();
aSet.load(security->selector(), i18n("Security"), m_account.accountList(), true);
// memo
memo->setText(m_split.memo());
d->m_activity->m_memoText = m_split.memo();
d->m_activity->m_memoChanged = false;
if (!isMultiSelection()) {
// date
if (m_transaction.postDate().isValid())
postDate->setDate(m_transaction.postDate());
else if (m_lastPostDate.isValid())
postDate->setDate(m_lastPostDate);
else
postDate->setDate(QDate::currentDate());
// security (but only if it's not the investment account)
if (m_split.accountId() != m_account.id()) {
security->completion()->setSelected(m_split.accountId());
security->slotItemSelected(m_split.accountId());
}
// activity
activity->setActivity(d->m_activity->type());
slotUpdateActivity(activity->activity());
asset->completion()->setSelected(m_assetAccountSplit.accountId());
asset->slotItemSelected(m_assetAccountSplit.accountId());
// interest-account
aSet.clear();
- aSet.addAccountGroup(MyMoneyAccount::Income);
+ aSet.addAccountGroup(eMyMoney::Account::Income);
aSet.load(interest->selector());
setupCategoryWidget(interest, m_interestSplits, id, SLOT(slotEditInterestSplits()));
slotUpdateInterestVisibility(interest->currentText());
// fee-account
aSet.clear();
- aSet.addAccountGroup(MyMoneyAccount::Expense);
+ aSet.addAccountGroup(eMyMoney::Account::Expense);
aSet.load(fees->selector());
setupCategoryWidget(fees, m_feeSplits, id, SLOT(slotEditFeeSplits()));
slotUpdateFeeVisibility(fees->currentText());
// shares
// don't set the value if the number of shares is zero so that
// we can see the hint
value = dynamic_cast<kMyMoneyEdit*>(haveWidget("shares"));
if (typeid(*(d->m_activity)) != typeid(Invest::Split(this)))
value->setPrecision(MyMoneyMoney::denomToPrec(m_security.smallestAccountFraction()));
else
value->setPrecision(-1);
if (!m_split.shares().isZero())
value->setValue(m_split.shares().abs());
// price
updatePriceMode(m_split);
// fee amount
value = dynamic_cast<kMyMoneyEdit*>(haveWidget("fee-amount"));
value->setValue(subtotal(m_feeSplits));
// interest amount
value = dynamic_cast<kMyMoneyEdit*>(haveWidget("interest-amount"));
value->setValue(-subtotal(m_interestSplits));
// total
slotUpdateTotalAmount();
// status
if (m_split.reconcileFlag() == MyMoneySplit::Unknown)
m_split.setReconcileFlag(MyMoneySplit::NotReconciled);
reconcile->setState(m_split.reconcileFlag());
} else {
postDate->loadDate(QDate());
reconcile->setState(MyMoneySplit::Unknown);
// We don't allow to change the activity
activity->setActivity(d->m_activity->type());
slotUpdateActivity(activity->activity());
activity->setDisabled(true);
// scan the list of selected transactions and check that they have
// the same activity.
KMyMoneyRegister::SelectedTransactions::iterator it_t = m_transactions.begin();
const QString& action = m_item->split().action();
bool isNegative = m_item->split().shares().isNegative();
bool allSameActivity = true;
for (it_t = m_transactions.begin(); allSameActivity && (it_t != m_transactions.end()); ++it_t) {
allSameActivity = (action == (*it_t).split().action() && (*it_t).split().shares().isNegative() == isNegative);
}
QStringList fields;
fields << "shares" << "price" << "fee-amount" << "interest-amount";
QStringList::const_iterator it_f;
for (it_f = fields.constBegin(); it_f != fields.constEnd(); ++it_f) {
value = dynamic_cast<kMyMoneyEdit*>(haveWidget((*it_f)));
value->setText("");
value->setAllowEmpty();
}
// if we have transactions with different activities, disable some more widgets
if (!allSameActivity) {
fields << "asset-account" << "fee-account" << "interest-account";
QStringList::const_iterator it_f;
for (it_f = fields.constBegin(); it_f != fields.constEnd(); ++it_f) {
haveWidget(*it_f)->setDisabled(true);
}
}
}
}
QWidget* InvestTransactionEditor::firstWidget() const
{
return 0; // let the creator use the first widget in the tab order
}
bool InvestTransactionEditor::isComplete(QString& reason) const
{
reason.clear();
return d->m_activity->isComplete(reason);
}
MyMoneyMoney InvestTransactionEditor::subtotal(const QList<MyMoneySplit>& splits) const
{
QList<MyMoneySplit>::const_iterator it_s;
MyMoneyMoney sum;
for (it_s = splits.begin(); it_s != splits.end(); ++it_s) {
sum += (*it_s).value();
}
return sum;
}
void InvestTransactionEditor::slotUpdateSecurity(const QString& stockId)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount stock = file->account(stockId);
m_security = file->security(stock.currencyId());
m_currency = file->security(m_security.tradingCurrency());
bool currencyKnown = !m_currency.id().isEmpty();
if (!currencyKnown) {
m_currency.setTradingSymbol("???");
} else {
if (typeid(*(d->m_activity)) != typeid(Invest::Split(this))) {
dynamic_cast<kMyMoneyEdit*>(haveWidget("shares"))->setPrecision(MyMoneyMoney::denomToPrec(m_security.smallestAccountFraction()));
} else {
dynamic_cast<kMyMoneyEdit*>(haveWidget("shares"))->setPrecision(-1);
}
}
updatePriceMode();
d->m_activity->preloadAssetAccount();
haveWidget("shares")->setEnabled(currencyKnown);
haveWidget("price")->setEnabled(currencyKnown);
haveWidget("fee-amount")->setEnabled(currencyKnown);
haveWidget("interest-amount")->setEnabled(currencyKnown);
slotUpdateTotalAmount();
slotUpdateButtonState();
resizeForm();
}
void InvestTransactionEditor::totalAmount(MyMoneyMoney& amount) const
{
KMyMoneyActivityCombo* activityCombo = dynamic_cast<KMyMoneyActivityCombo*>(haveWidget("activity"));
kMyMoneyEdit* sharesEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("shares"));
kMyMoneyEdit* priceEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("price"));
kMyMoneyEdit* feesEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("fee-amount"));
kMyMoneyEdit* interestEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("interest-amount"));
if (priceMode() == InvestTransactionEditor::PricePerTransaction)
amount = priceEdit->value().abs();
else
amount = sharesEdit->value().abs() * priceEdit->value().abs();
if (feesEdit->isVisible()) {
MyMoneyMoney fee = feesEdit->value();
MyMoneyMoney factor(-1, 1);
switch (activityCombo->activity()) {
case MyMoneySplit::BuyShares:
case MyMoneySplit::ReinvestDividend:
factor = MyMoneyMoney::ONE;
break;
default:
break;
}
amount += (fee * factor);
}
if (interestEdit->isVisible()) {
MyMoneyMoney interest = interestEdit->value();
MyMoneyMoney factor(1, 1);
switch (activityCombo->activity()) {
case MyMoneySplit::BuyShares:
factor = MyMoneyMoney::MINUS_ONE;
break;
default:
break;
}
amount += (interest * factor);
}
}
void InvestTransactionEditor::slotUpdateTotalAmount()
{
QLabel* total = dynamic_cast<QLabel*>(haveWidget("total"));
if (total && total->isVisible()) {
MyMoneyMoney amount;
totalAmount(amount);
total->setText(amount.convert(m_currency.smallestAccountFraction(), static_cast<MyMoneyMoney::roundingMethod>(m_security.roundingMethod()))
.formatMoney(m_currency.tradingSymbol(), MyMoneyMoney::denomToPrec(m_currency.smallestAccountFraction())));
}
}
void InvestTransactionEditor::slotTransactionContainerGeometriesUpdated()
{
// when the geometries of the transaction container are updated some edit widgets that were
// previously hidden are being shown (see QAbstractItemView::updateEditorGeometries) so we
// need to update the activity with the current activity in order to show only the widgets
// which are needed by the current activity
slotUpdateActivity(d->m_activity->type());
}
void InvestTransactionEditor::slotUpdateActivity(MyMoneySplit::investTransactionTypeE activity)
{
// create new activity object if required
activityFactory(activity);
// hide all dynamic widgets
d->hideCategory("interest-account");
d->hideCategory("fee-account");
QStringList dynwidgets;
dynwidgets << "total-label" << "asset-label" << "fee-label" << "fee-amount-label" << "interest-label" << "interest-amount-label" << "price-label" << "shares-label";
// hiding labels works by clearing them. hide() does not do the job
// as the underlying text in the QTable object will shine through
QStringList::const_iterator it_s;
for (it_s = dynwidgets.constBegin(); it_s != dynwidgets.constEnd(); ++it_s) {
QLabel* w = dynamic_cast<QLabel*>(haveWidget(*it_s));
if (w)
w->setText(" ");
}
// real widgets can be hidden
dynwidgets.clear();
dynwidgets << "asset-account" << "interest-amount" << "fee-amount" << "shares" << "price" << "total";
for (it_s = dynwidgets.constBegin(); it_s != dynwidgets.constEnd(); ++it_s) {
QWidget* w = haveWidget(*it_s);
if (w)
w->hide();
}
d->m_activity->showWidgets();
d->m_activity->preloadAssetAccount();
if (KMyMoneyCategory* cat = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account"))) {
if (cat->parentWidget()->isVisible())
slotUpdateInterestVisibility(cat->currentText());
else
cat->splitButton()->hide();
}
if (KMyMoneyCategory* cat = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account"))) {
if (cat->parentWidget()->isVisible())
slotUpdateFeeVisibility(cat->currentText());
else
cat->splitButton()->hide();
}
}
InvestTransactionEditor::priceModeE InvestTransactionEditor::priceMode() const
{
priceModeE mode = static_cast<priceModeE>(Price);
KMyMoneySecurity* sec = dynamic_cast<KMyMoneySecurity*>(m_editWidgets["security"]);
QString accId;
if (!sec->currentText().isEmpty()) {
accId = sec->selectedItem();
if (accId.isEmpty())
accId = m_account.id();
}
while (!accId.isEmpty() && mode == Price) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(accId);
if (acc.value("priceMode").isEmpty())
accId = acc.parentAccountId();
else
mode = static_cast<priceModeE>(acc.value("priceMode").toInt());
}
// if mode is still <Price> then use that
if (mode == Price)
mode = PricePerShare;
return mode;
}
bool InvestTransactionEditor::setupPrice(const MyMoneyTransaction& t, MyMoneySplit& split)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount acc = file->account(split.accountId());
MyMoneySecurity toCurrency(file->security(acc.currencyId()));
int fract = acc.fraction();
if (acc.currencyId() != t.commodity()) {
if (acc.currencyId().isEmpty())
acc.setCurrencyId(t.commodity());
QMap<QString, MyMoneyMoney>::Iterator it_p;
QString key = t.commodity() + '-' + acc.currencyId();
it_p = m_priceInfo.find(key);
// if it's not found, then collect it from the user first
MyMoneyMoney price;
if (it_p == m_priceInfo.end()) {
MyMoneySecurity fromCurrency = file->security(t.commodity());
MyMoneyMoney fromValue, toValue;
fromValue = split.value();
const MyMoneyPrice &priceInfo = MyMoneyFile::instance()->price(fromCurrency.id(), toCurrency.id(), t.postDate());
toValue = split.value() * priceInfo.rate(toCurrency.id());
QPointer<KCurrencyCalculator> calc =
new KCurrencyCalculator(fromCurrency,
toCurrency,
fromValue,
toValue,
t.postDate(),
fract,
m_regForm);
if (calc->exec() == QDialog::Rejected) {
delete calc;
return false;
}
price = calc->price();
delete calc;
m_priceInfo[key] = price;
} else {
price = (*it_p);
}
// update shares if the transaction commodity is the currency
// of the current selected account
split.setShares(split.value() * price);
} else {
split.setShares(split.value());
}
return true;
}
bool InvestTransactionEditor::createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool /* skipPriceDialog */)
{
MyMoneyFile* file = MyMoneyFile::instance();
// we start with the previous values, make sure we can add them later on
t = torig;
MyMoneySplit s0 = sorig;
s0.clearId();
KMyMoneySecurity* sec = dynamic_cast<KMyMoneySecurity*>(m_editWidgets["security"]);
if (!isMultiSelection() || (isMultiSelection() && !sec->currentText().isEmpty())) {
QString securityId = sec->selectedItem();
if (!securityId.isEmpty()) {
s0.setAccountId(securityId);
MyMoneyAccount stockAccount = file->account(securityId);
QString currencyId = stockAccount.currencyId();
MyMoneySecurity security = file->security(currencyId);
t.setCommodity(security.tradingCurrency());
} else {
s0.setAccountId(m_account.id());
t.setCommodity(m_account.currencyId());
}
}
// extract price info from original transaction
m_priceInfo.clear();
QList<MyMoneySplit>::const_iterator it_s;
if (!torig.id().isEmpty()) {
for (it_s = torig.splits().begin(); it_s != torig.splits().end(); ++it_s) {
if ((*it_s).id() != sorig.id()) {
MyMoneyAccount cat = file->account((*it_s).accountId());
if (cat.currencyId() != m_account.currencyId()) {
if (cat.currencyId().isEmpty())
cat.setCurrencyId(m_account.currencyId());
if (!(*it_s).shares().isZero() && !(*it_s).value().isZero()) {
m_priceInfo[cat.currencyId()] = ((*it_s).shares() / (*it_s).value()).reduce();
}
}
}
}
}
t.removeSplits();
kMyMoneyDateInput* postDate = dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"]);
if (postDate->date().isValid()) {
t.setPostDate(postDate->date());
}
// memo and number field are special: if we have multiple transactions selected
// and the edit field is empty, we treat it as "not modified".
// FIXME a better approach would be to have a 'dirty' flag with the widgets
// which identifies if the originally loaded value has been modified
// by the user
KTextEdit* memo = dynamic_cast<KTextEdit*>(m_editWidgets["memo"]);
if (memo) {
if (!isMultiSelection() || (isMultiSelection() && d->m_activity->m_memoChanged))
s0.setMemo(memo->toPlainText());
}
MyMoneySplit assetAccountSplit;
QList<MyMoneySplit> feeSplits;
QList<MyMoneySplit> interestSplits;
MyMoneySecurity security, currency;
MyMoneySplit::investTransactionTypeE transactionType;
// extract the splits from the original transaction
KMyMoneyUtils::dissectTransaction(torig, sorig,
assetAccountSplit,
feeSplits,
interestSplits,
security,
currency,
transactionType);
// check if the trading currency is the same if the security has changed
// in case it differs, check that we have a price (request from user)
// and convert all splits
// TODO
// do the conversions here
// TODO
// keep the current activity object and create a new one
// that can be destroyed later on
Activity* activity = d->m_activity;
d->m_activity = 0; // make sure we create a new one
activityFactory(activity->type());
// if the activity is not set in the combo widget, we keep
// the one which is used in the original transaction
KMyMoneyActivityCombo* activityCombo = dynamic_cast<KMyMoneyActivityCombo*>(haveWidget("activity"));
if (activityCombo->activity() == MyMoneySplit::UnknownTransactionType) {
activityFactory(transactionType);
}
// if we mark the split reconciled here, we'll use today's date if no reconciliation date is given
KMyMoneyReconcileCombo* status = dynamic_cast<KMyMoneyReconcileCombo*>(m_editWidgets["status"]);
if (status->state() != MyMoneySplit::Unknown)
s0.setReconcileFlag(status->state());
if (s0.reconcileFlag() == MyMoneySplit::Reconciled && !s0.reconcileDate().isValid())
s0.setReconcileDate(QDate::currentDate());
// call the creation logic for the current selected activity
bool rc = d->m_activity->createTransaction(t, s0, assetAccountSplit, feeSplits, m_feeSplits, interestSplits, m_interestSplits, security, currency);
// now switch back to the original activity
delete d->m_activity;
d->m_activity = activity;
// add the splits to the transaction
if (rc) {
if (security.name().isEmpty()) // new transaction has no security filled...
security = file->security(file->account(s0.accountId()).currencyId()); // ...so fetch it from s0 split
QList<MyMoneySplit> resultSplits; // concatenates splits for easy processing
if (!assetAccountSplit.accountId().isEmpty())
resultSplits.append(assetAccountSplit);
if (!feeSplits.isEmpty())
resultSplits.append(feeSplits);
if (!interestSplits.isEmpty())
resultSplits.append(interestSplits);
AlkValue::RoundingMethod roundingMethod = AlkValue::RoundRound;
if (security.roundingMethod() != AlkValue::RoundNever)
roundingMethod = security.roundingMethod();
int currencyFraction = currency.smallestAccountFraction();
int securityFraction = security.smallestAccountFraction();
// assuming that all non-stock splits are monetary
foreach (auto split, resultSplits) {
split.clearId();
split.setShares(split.shares().convertDenominator(currencyFraction, roundingMethod));
split.setValue(split.value().convertDenominator(currencyFraction, roundingMethod));
t.addSplit(split);
}
s0.setShares(s0.shares().convertDenominator(securityFraction, roundingMethod)); // only shares variable from stock split isn't evaluated in currency
s0.setValue(s0.value().convertDenominator(currencyFraction, roundingMethod));
t.addSplit(s0);
}
return rc;
}
void InvestTransactionEditor::updatePriceMode(const MyMoneySplit& split)
{
QLabel* label = dynamic_cast<QLabel*>(haveWidget("price-label"));
if (label) {
kMyMoneyEdit* sharesEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("shares"));
kMyMoneyEdit* priceEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("price"));
MyMoneyMoney price;
if (!split.id().isEmpty())
price = split.price().reduce();
else
price = priceEdit->value().abs();
if (priceMode() == PricePerTransaction) {
priceEdit->setPrecision(m_currency.pricePrecision());
label->setText(i18n("Transaction amount"));
if (!sharesEdit->value().isZero())
priceEdit->setValue(sharesEdit->value().abs() * price);
} else if (priceMode() == PricePerShare) {
priceEdit->setPrecision(m_security.pricePrecision());
label->setText(i18n("Price/Share"));
priceEdit->setValue(price);
} else
priceEdit->setValue(price);
}
}
void InvestTransactionEditor::setupFinalWidgets()
{
addFinalWidget(haveWidget("memo"));
}
void InvestTransactionEditor::slotUpdateInvestMemoState()
{
KTextEdit* memo = dynamic_cast<KTextEdit*>(m_editWidgets["memo"]);
if (memo) {
d->m_activity->m_memoChanged = (memo->toPlainText() != d->m_activity->m_memoText);
}
}
diff --git a/kmymoney/dialogs/kaccountselectdlg.cpp b/kmymoney/dialogs/kaccountselectdlg.cpp
index a19ec825d..0bfaa5fc0 100644
--- a/kmymoney/dialogs/kaccountselectdlg.cpp
+++ b/kmymoney/dialogs/kaccountselectdlg.cpp
@@ -1,197 +1,197 @@
/***************************************************************************
kaccountselectdlg.cpp - description
-------------------
begin : Mon Feb 10 2003
copyright : (C) 2000-2003 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "kaccountselectdlg.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QLabel>
#include <QPushButton>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KGuiItem>
#include <KStandardGuiItem>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
#include "kmymoneycategory.h"
#include "kmymoneyaccountselector.h"
#include <../kmymoney.h>
#include "icons/icons.h"
using namespace Icons;
KAccountSelectDlg::KAccountSelectDlg(const KMyMoneyUtils::categoryTypeE accountType, const QString& purpose, QWidget *parent)
: KAccountSelectDlgDecl(parent),
m_purpose(purpose),
m_accountType(accountType),
m_aborted(false)
{
// Hide the abort button. It needs to be shown on request by the caller
// using showAbortButton()
m_kButtonAbort->hide();
slotReloadWidget();
KGuiItem skipButtonItem(i18n("&Skip"),
QIcon::fromTheme(g_Icons[Icon::MediaSkipForward]),
i18n("Skip this transaction"),
i18n("Use this to skip importing this transaction and proceed with the next one."));
KGuiItem::assign(m_qbuttonCancel, skipButtonItem);
KGuiItem createButtenItem(i18n("&Create..."),
QIcon::fromTheme(g_Icons[Icon::DocumentNew]),
i18n("Create a new account/category"),
i18n("Use this to add a new account/category to the file"));
KGuiItem::assign(m_createButton, createButtenItem);
KGuiItem::assign(m_qbuttonOk, KStandardGuiItem::ok());
KGuiItem abortButtenItem(i18n("&Abort"),
QIcon::fromTheme(g_Icons[Icon::DialogCancel]),
i18n("Abort the import operation and dismiss all changes"),
i18n("Use this to abort the import. Your financial data will be in the state before you started the QIF import."));
KGuiItem::assign(m_kButtonAbort, abortButtenItem);
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotReloadWidget()));
connect(m_createButton, SIGNAL(clicked()), this, SLOT(slotCreateAccount()));
connect(m_qbuttonOk, SIGNAL(clicked()), this, SLOT(accept()));
connect(m_qbuttonCancel, SIGNAL(clicked()), this, SLOT(reject()));
connect(m_kButtonAbort, SIGNAL(clicked()), this, SLOT(abort()));
}
KAccountSelectDlg::~KAccountSelectDlg()
{
}
void KAccountSelectDlg::slotReloadWidget()
{
AccountSet set;
if (m_accountType & KMyMoneyUtils::asset)
- set.addAccountGroup(MyMoneyAccount::Asset);
+ set.addAccountGroup(eMyMoney::Account::Asset);
if (m_accountType & KMyMoneyUtils::liability)
- set.addAccountGroup(MyMoneyAccount::Liability);
+ set.addAccountGroup(eMyMoney::Account::Liability);
if (m_accountType & KMyMoneyUtils::income)
- set.addAccountGroup(MyMoneyAccount::Income);
+ set.addAccountGroup(eMyMoney::Account::Income);
if (m_accountType & KMyMoneyUtils::expense)
- set.addAccountGroup(MyMoneyAccount::Expense);
+ set.addAccountGroup(eMyMoney::Account::Expense);
if (m_accountType & KMyMoneyUtils::equity)
- set.addAccountGroup(MyMoneyAccount::Equity);
+ set.addAccountGroup(eMyMoney::Account::Equity);
if (m_accountType & KMyMoneyUtils::checking)
- set.addAccountType(MyMoneyAccount::Checkings);
+ set.addAccountType(eMyMoney::Account::Checkings);
if (m_accountType & KMyMoneyUtils::savings)
- set.addAccountType(MyMoneyAccount::Savings);
+ set.addAccountType(eMyMoney::Account::Savings);
if (m_accountType & KMyMoneyUtils::investment)
- set.addAccountType(MyMoneyAccount::Investment);
+ set.addAccountType(eMyMoney::Account::Investment);
if (m_accountType & KMyMoneyUtils::creditCard)
- set.addAccountType(MyMoneyAccount::CreditCard);
+ set.addAccountType(eMyMoney::Account::CreditCard);
set.load(m_accountSelector->selector());
}
void KAccountSelectDlg::setDescription(const QString& msg)
{
m_descLabel->setText(msg);
}
void KAccountSelectDlg::setHeader(const QString& msg)
{
m_headerLabel->setText(msg);
}
void KAccountSelectDlg::setAccount(const MyMoneyAccount& account, const QString& id)
{
m_account = account;
m_accountSelector->setSelectedItem(id);
}
void KAccountSelectDlg::slotCreateInstitution()
{
kmymoney->slotInstitutionNew();
}
void KAccountSelectDlg::slotCreateAccount()
{
if (!(m_accountType & (KMyMoneyUtils::expense | KMyMoneyUtils::income))) {
kmymoney->slotAccountNew(m_account);
if (!m_account.id().isEmpty()) {
slotReloadWidget();
m_accountSelector->setSelectedItem(m_account.id());
accept();
}
} else {
- if (m_account.accountType() == MyMoneyAccount::Expense)
+ if (m_account.accountType() == eMyMoney::Account::Expense)
kmymoney->createCategory(m_account, MyMoneyFile::instance()->expense());
else
kmymoney->createCategory(m_account, MyMoneyFile::instance()->income());
if (!m_account.id().isEmpty()) {
slotReloadWidget();
m_accountSelector->setSelectedItem(m_account.id());
accept();
}
}
}
void KAccountSelectDlg::abort()
{
m_aborted = true;
reject();
}
void KAccountSelectDlg::setMode(const int mode)
{
m_mode = mode ? 1 : 0;
}
void KAccountSelectDlg::showAbortButton(const bool visible)
{
m_kButtonAbort->setVisible(visible);
}
int KAccountSelectDlg::exec()
{
int rc = Rejected;
if (m_mode == 1) {
slotCreateAccount();
rc = result();
}
if (rc != Accepted) {
m_createButton->setFocus();
rc = KAccountSelectDlgDecl::exec();
}
return rc;
}
const QString& KAccountSelectDlg::selectedAccount() const
{
return m_accountSelector->selectedItem();
}
diff --git a/kmymoney/dialogs/kbalancechartdlg.cpp b/kmymoney/dialogs/kbalancechartdlg.cpp
index 37a21785a..5d5f5388c 100644
--- a/kmymoney/dialogs/kbalancechartdlg.cpp
+++ b/kmymoney/dialogs/kbalancechartdlg.cpp
@@ -1,163 +1,163 @@
/***************************************************************************
kbalancechartdlg - description
-------------------
begin : Mon Nov 26 2007
copyright : (C) 2007 by Thomas Baumgart
email : Thomas Baumgart <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 "kbalancechartdlg.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <QWindow>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KSharedConfig>
#include <KWindowConfig>
#include <KConfigGroup>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyreport.h"
#include "pivottable.h"
#include "kreportchartview.h"
using namespace reports;
KBalanceChartDlg::KBalanceChartDlg(const MyMoneyAccount& account, QWidget* parent) :
QDialog(parent)
{
setWindowTitle(i18n("Balance of %1", account.name()));
setSizeGripEnabled(true);
setModal(true);
// restore the last used dialog size
winId(); // needs to be called to create the QWindow
KConfigGroup grp = KSharedConfig::openConfig()->group("KBalanceChartDlg");
if (grp.isValid()) {
KWindowConfig::restoreWindowSize(windowHandle(), grp);
}
// let the minimum size be 700x500
resize(QSize(700, 500).expandedTo(windowHandle() ? windowHandle()->size() : QSize()));
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
//draw the chart and add it to the main layout
KReportChartView* chartWidget = drawChart(account);
mainLayout->addWidget(chartWidget);
// add the buttons
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
mainLayout->addWidget(buttonBox);
}
KBalanceChartDlg::~KBalanceChartDlg()
{
// store the last used dialog size
KConfigGroup grp = KSharedConfig::openConfig()->group("KBalanceChartDlg");
if (grp.isValid()) {
KWindowConfig::saveWindowSize(windowHandle(), grp);
}
}
KReportChartView* KBalanceChartDlg::drawChart(const MyMoneyAccount& account)
{
MyMoneyReport reportCfg = MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::last3ToNext3Months,
MyMoneyReport::eDetailTotal,
i18n("%1 Balance History", account.name()),
i18n("Generated Report")
);
reportCfg.setChartByDefault(true);
reportCfg.setChartCHGridLines(false);
reportCfg.setChartSVGridLines(false);
reportCfg.setChartDataLabels(false);
reportCfg.setChartType(MyMoneyReport::eChartLine);
reportCfg.setIncludingForecast(true);
reportCfg.setIncludingBudgetActuals(true);
- if (account.accountType() == MyMoneyAccount::Investment) {
+ if (account.accountType() == eMyMoney::Account::Investment) {
QStringList::const_iterator it_a;
for (it_a = account.accountList().begin(); it_a != account.accountList().end(); ++it_a)
reportCfg.addAccount(*it_a);
} else
reportCfg.addAccount(account.id());
reportCfg.setColumnsAreDays(true);
reportCfg.setConvertCurrency(false);
reportCfg.setMixedTime(true);
reports::PivotTable table(reportCfg);
reports::KReportChartView* chartWidget = new reports::KReportChartView(this);
table.drawChart(*chartWidget);
// add another row for limit
bool needRow = false;
bool haveMinBalance = false;
bool haveMaxCredit = false;
MyMoneyMoney minBalance, maxCredit;
MyMoneyMoney factor(1, 1);
- if (account.accountGroup() == MyMoneyAccount::Asset)
+ if (account.accountGroup() == eMyMoney::Account::Asset)
factor = -factor;
if (!account.value("maxCreditEarly").isEmpty()) {
needRow = true;
haveMaxCredit = true;
maxCredit = MyMoneyMoney(account.value("maxCreditEarly")) * factor;
}
if (!account.value("maxCreditAbsolute").isEmpty()) {
needRow = true;
haveMaxCredit = true;
maxCredit = MyMoneyMoney(account.value("maxCreditAbsolute")) * factor;
}
if (!account.value("minBalanceEarly").isEmpty()) {
needRow = true;
haveMinBalance = true;
minBalance = MyMoneyMoney(account.value("minBalanceEarly"));
}
if (!account.value("minBalanceAbsolute").isEmpty()) {
needRow = true;
haveMinBalance = true;
minBalance = MyMoneyMoney(account.value("minBalanceAbsolute"));
}
if (needRow) {
if (haveMinBalance) {
chartWidget->drawLimitLine(minBalance.toDouble());
}
if (haveMaxCredit) {
chartWidget->drawLimitLine(maxCredit.toDouble());
}
}
// always draw the y axis zero value line
// TODO: port to KF5 - this crashes KChart
//chartWidget->drawLimitLine(0);
//remove the legend
chartWidget->removeLegend();
return chartWidget;
}
diff --git a/kmymoney/dialogs/kcategoryreassigndlg.cpp b/kmymoney/dialogs/kcategoryreassigndlg.cpp
index 48eef8035..25279a47f 100644
--- a/kmymoney/dialogs/kcategoryreassigndlg.cpp
+++ b/kmymoney/dialogs/kcategoryreassigndlg.cpp
@@ -1,101 +1,101 @@
/***************************************************************************
kcategoryreassigndlg.cpp
-------------------
copyright : (C) 2007 by Thomas Baumgart <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 "kcategoryreassigndlg.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QPushButton>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KMessageBox>
#include <kguiutils.h>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
#include "kmymoneycategory.h"
#include "kmymoneyaccountselector.h"
KCategoryReassignDlg::KCategoryReassignDlg(QWidget* parent) :
KCategoryReassignDlgDecl(parent)
{
kMandatoryFieldGroup* mandatory = new kMandatoryFieldGroup(this);
mandatory->add(m_category);
mandatory->setOkButton(buttonBox->button(QDialogButtonBox::Ok));
}
KCategoryReassignDlg::~KCategoryReassignDlg()
{
}
QString KCategoryReassignDlg::show(const MyMoneyAccount& category)
{
if (category.id().isEmpty())
return QString(); // no payee available? nothing can be selected...
AccountSet set;
- set.addAccountGroup(MyMoneyAccount::Income);
- set.addAccountGroup(MyMoneyAccount::Expense);
+ set.addAccountGroup(eMyMoney::Account::Income);
+ set.addAccountGroup(eMyMoney::Account::Expense);
set.load(m_category->selector());
// remove the category we are about to delete
m_category->selector()->removeItem(category.id());
// make sure the available categories have the same currency
QStringList list;
QStringList::const_iterator it_a;
m_category->selector()->itemList(list);
for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a);
if (acc.currencyId() != category.currencyId())
m_category->selector()->removeItem(*it_a);
}
// reload the list
m_category->selector()->itemList(list);
// if there is no category for reassignment left, we bail out
if (list.isEmpty()) {
KMessageBox::sorry(this, QString("<qt>") + i18n("At least one transaction/schedule still references the category <b>%1</b>. However, at least one category with the same currency must exist so that the transactions/schedules can be reassigned.", category.name()) + QString("</qt>"));
return QString();
}
// execute dialog and if aborted, return empty string
if (this->exec() == QDialog::Rejected)
return QString();
// otherwise return index of selected payee
return m_category->selectedItem();
}
void KCategoryReassignDlg::accept()
{
// force update of payeeCombo
buttonBox->button(QDialogButtonBox::Ok)->setFocus();
if (m_category->selectedItem().isEmpty()) {
KMessageBox::information(this, i18n("This dialog does not allow new categories to be created. Please pick a category from the list."), i18n("Category creation"));
} else {
KCategoryReassignDlgDecl::accept();
}
}
diff --git a/kmymoney/dialogs/kconfirmmanualenterdlg.cpp b/kmymoney/dialogs/kconfirmmanualenterdlg.cpp
index 84e7e1dff..7c7d876dd 100644
--- a/kmymoney/dialogs/kconfirmmanualenterdlg.cpp
+++ b/kmymoney/dialogs/kconfirmmanualenterdlg.cpp
@@ -1,186 +1,187 @@
/***************************************************************************
kconfirmmanualenterdlg.cpp
-------------------
begin : Mon Apr 9 2007
copyright : (C) 2007 by Thomas Baumgart
email : Thomas Baumgart <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 "kconfirmmanualenterdlg.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QButtonGroup>
#include <QRadioButton>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KTextEdit>
#include <KMessageBox>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneypayee.h"
#include "kmymoneyutils.h"
#include "mymoneytransaction.h"
#include "ui_kconfirmmanualenterdlgdecl.h"
struct KConfirmManualEnterDlg::Private {
Ui::KConfirmManualEnterDlgDecl ui;
};
KConfirmManualEnterDlg::KConfirmManualEnterDlg(const MyMoneySchedule& schedule, QWidget* parent) :
QDialog(parent), d(new Private)
{
d->ui.setupUi(this);
d->ui.buttonGroup1->setId(d->ui.m_discardRadio, 0);
d->ui.buttonGroup1->setId(d->ui.m_onceRadio, 1);
d->ui.buttonGroup1->setId(d->ui.m_setRadio, 2);
d->ui.m_onceRadio->setChecked(true);
- if (schedule.type() == MyMoneySchedule::TYPE_LOANPAYMENT) {
+ if (schedule.type() == eMyMoney::Schedule::Type::LoanPayment) {
d->ui.m_setRadio->setEnabled(false);
d->ui.m_discardRadio->setEnabled(false);
}
}
KConfirmManualEnterDlg::~KConfirmManualEnterDlg()
{
delete d;
}
void KConfirmManualEnterDlg::loadTransactions(const MyMoneyTransaction& to, const MyMoneyTransaction& tn)
{
QString messageDetail("<qt>");
MyMoneyFile* file = MyMoneyFile::instance();
int noItemsChanged = 0;
try {
if (to.splits().isEmpty())
throw MYMONEYEXCEPTION(i18n("Transaction %1 has no splits", to.id()));
if (tn.splits().isEmpty())
throw MYMONEYEXCEPTION(i18n("Transaction %1 has no splits", tn.id()));
QString po, pn;
if (!to.splits().front().payeeId().isEmpty())
po = file->payee(to.splits().front().payeeId()).name();
if (!tn.splits().front().payeeId().isEmpty())
pn = file->payee(tn.splits().front().payeeId()).name();
if (po != pn) {
noItemsChanged++;
messageDetail += i18n("<p>Payee changed.<br/>&nbsp;&nbsp;&nbsp;Old: <b>%1</b>, New: <b>%2</b></p>", po, pn);
}
if (to.splits().front().accountId() != tn.splits().front().accountId()) {
noItemsChanged++;
messageDetail += i18n("<p>Account changed.<br/>&nbsp;&nbsp;&nbsp;Old: <b>%1</b>, New: <b>%2</b></p>"
, file->account(to.splits().front().accountId()).name()
, file->account(tn.splits().front().accountId()).name());
}
if (file->isTransfer(to) && file->isTransfer(tn)) {
if (to.splits()[1].accountId() != tn.splits()[1].accountId()) {
noItemsChanged++;
messageDetail += i18n("<p>Transfer account changed.<br/>&nbsp;&nbsp;&nbsp;Old: <b>%1</b>, New: <b>%2</b></p>"
, file->account(to.splits()[1].accountId()).name()
, file->account(tn.splits()[1].accountId()).name());
}
} else {
QString co, cn;
switch (to.splitCount()) {
default:
co = i18nc("Split transaction (category replacement)", "Split transaction");
break;
case 2:
co = file->accountToCategory(to.splits()[1].accountId());
case 1:
break;
}
switch (tn.splitCount()) {
default:
cn = i18nc("Split transaction (category replacement)", "Split transaction");
break;
case 2:
cn = file->accountToCategory(tn.splits()[1].accountId());
case 1:
break;
}
if (co != cn) {
noItemsChanged++;
messageDetail += i18n("<p>Category changed.<br/>&nbsp;&nbsp;&nbsp;Old: <b>%1</b>, New: <b>%2</b></p>", co, cn);
}
}
QString mo, mn;
mo = to.splits().front().memo();
mn = tn.splits().front().memo();
if (mo.isEmpty())
mo = QString("<i>") + i18nc("Empty memo", "empty") + QString("</i>");
if (mn.isEmpty())
mn = QString("<i>") + i18nc("Empty memo", "empty") + QString("</i>");
if (mo != mn) {
noItemsChanged++;
messageDetail += i18n("<p>Memo changed.<br/>&nbsp;&nbsp;&nbsp;Old: <b>%1</b>, New: <b>%2</b></p>", mo, mn);
}
QString no, nn;
no = to.splits().front().number();
nn = tn.splits().front().number();
if (no.isEmpty())
no = QString("<i>") + i18nc("No number", "empty") + QString("</i>");
if (nn.isEmpty())
nn = QString("<i>") + i18nc("No number", "empty") + QString("</i>");
if (no != nn) {
noItemsChanged++;
messageDetail += i18n("<p>Number changed.<br/>&nbsp;&nbsp;&nbsp;Old: <b>%1</b>, New: <b>%2</b></p>", no, nn);
}
const MyMoneySecurity& sec = MyMoneyFile::instance()->security(to.commodity());
MyMoneyMoney ao, an;
ao = to.splits().front().value();
an = tn.splits().front().value();
if (ao != an) {
noItemsChanged++;
messageDetail += i18n("<p>Amount changed.<br/>&nbsp;&nbsp;&nbsp;Old: <b>%1</b>, New: <b>%2</b></p>", ao.formatMoney(sec.smallestAccountFraction()), an.formatMoney(sec.smallestAccountFraction()));
}
MyMoneySplit::reconcileFlagE fo, fn;
fo = to.splits().front().reconcileFlag();
fn = tn.splits().front().reconcileFlag();
if (fo != fn) {
noItemsChanged++;
messageDetail += i18n("<p>Reconciliation flag changed.<br/>&nbsp;&nbsp;&nbsp;Old: <b>%1</b>, New: <b>%2</b></p>", KMyMoneyUtils::reconcileStateToString(fo, true), KMyMoneyUtils::reconcileStateToString(fn, true));
}
} catch (const MyMoneyException &e) {
KMessageBox::error(this, i18n("Fatal error in determining data: %1", e.what()));
}
messageDetail += "</qt>";
d->ui.m_details->setText(messageDetail);
return;
}
KConfirmManualEnterDlg::Action KConfirmManualEnterDlg::action() const
{
if (d->ui.m_discardRadio->isChecked())
return UseOriginal;
if (d->ui.m_setRadio->isChecked())
return ModifyAlways;
return ModifyOnce;
}
diff --git a/kmymoney/dialogs/kcurrencyeditdlg.cpp b/kmymoney/dialogs/kcurrencyeditdlg.cpp
index ca072b5d5..6fd928b11 100644
--- a/kmymoney/dialogs/kcurrencyeditdlg.cpp
+++ b/kmymoney/dialogs/kcurrencyeditdlg.cpp
@@ -1,385 +1,386 @@
/***************************************************************************
kcurrencyeditdlg.cpp - description
-------------------
begin : Wed Mar 24 2004
copyright : (C) 2000-2004 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@users.sourceforge.net>
Alvaro Soliverez <asoliverez@gmail.com>
(C) 2017 Łukasz Wojniłowicz <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 "kcurrencyeditdlg.h"
#include <locale.h>
// ----------------------------------------------------------------------------
// QT Includes
#include <QTimer>
#include <QPixmap>
#include <QBitmap>
#include <QList>
#include <QTreeWidget>
#include <QStyledItemDelegate>
#include <QIcon>
#include <QPushButton>
#include <QBitArray>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KTreeWidgetSearchLineWidget>
// ----------------------------------------------------------------------------
// Project Includes
#include "ui_kcurrencyeditdlg.h"
#include "ui_kcurrencyeditordlg.h"
#include "ui_kavailablecurrencydlg.h"
#include "mymoneysecurity.h"
#include "mymoneyfile.h"
+#include "mymoneyprice.h"
#include "kavailablecurrencydlg.h"
#include "kcurrencyeditordlg.h"
#include "kmymoneyutils.h"
#include "icons/icons.h"
#include "storageenums.h"
using namespace Icons;
// this delegate is needed to disable editing the currency id (column 1)
// since QTreeWidgetItem has only one set of flags for the whole row
// the column editable property couldn't be set in an easier way
class KCurrencyEditDelegate : public QStyledItemDelegate
{
public:
explicit KCurrencyEditDelegate(QObject *parent = 0);
protected:
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
KCurrencyEditDelegate::KCurrencyEditDelegate(QObject* parent): QStyledItemDelegate(parent)
{
}
QWidget *KCurrencyEditDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (index.column() == 1)
return 0;
return QStyledItemDelegate::createEditor(parent, option, index);
}
KCurrencyEditDlg::KCurrencyEditDlg(QWidget *parent) : ui(new Ui::KCurrencyEditDlg)
{
Q_UNUSED(parent);
ui->setupUi(this);
m_searchWidget = new KTreeWidgetSearchLineWidget(this, ui->m_currencyList);
m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
m_searchWidget->setFocus();
ui->verticalLayout->insertWidget(0, m_searchWidget);
ui->m_currencyList->setItemDelegate(new KCurrencyEditDelegate(ui->m_currencyList));
ui->m_closeButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogClose]));
ui->m_editCurrencyButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentEdit]));
ui->m_selectBaseCurrencyButton->setIcon(QIcon::fromTheme(g_Icons[Icon::KMyMoney]));
connect(ui->m_currencyList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotOpenContextMenu(QPoint)));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadCurrencies()));
connect(ui->m_currencyList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotUpdateCurrency(QTreeWidgetItem*)));
connect(ui->m_currencyList, SIGNAL(itemSelectionChanged()), this, SLOT(slotItemSelectionChanged()));
connect(ui->m_selectBaseCurrencyButton, SIGNAL(clicked()), this, SLOT(slotSelectBaseCurrency()));
connect(ui->m_addCurrencyButton, SIGNAL(clicked()), this, SLOT(slotAddCurrency()));
connect(ui->m_removeCurrencyButton, SIGNAL(clicked()), this, SLOT(slotRemoveCurrency()));
connect(ui->m_editCurrencyButton, SIGNAL(clicked()), this, SLOT(slotEditCurrency()));
connect(ui->m_removeUnusedCurrencyButton, SIGNAL(clicked()), this, SLOT(slotRemoveUnusedCurrency()));
QTimer::singleShot(10, this, SLOT(timerDone()));
}
void KCurrencyEditDlg::timerDone()
{
slotLoadCurrencies();
//resize the column widths
for (int i = 0; i < 3; ++i)
ui->m_currencyList->resizeColumnToContents(i);
if (!m_currency.id().isEmpty()) {
QTreeWidgetItemIterator it(ui->m_currencyList);
QTreeWidgetItem* q;
while ((q = *it) != 0) {
if (q->text(1) == m_currency.id()) {
ui->m_currencyList->scrollToItem(q);
break;
}
++it;
}
}
}
KCurrencyEditDlg::~KCurrencyEditDlg()
{
}
void KCurrencyEditDlg::slotLoadCurrencies()
{
disconnect(ui->m_currencyList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(slotSelectCurrency(QTreeWidgetItem*)));
disconnect(ui->m_currencyList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotUpdateCurrency(QTreeWidgetItem*)));
QList<MyMoneySecurity> list = MyMoneyFile::instance()->currencyList();
QList<MyMoneySecurity>::ConstIterator it;
QTreeWidgetItem *first = 0;
QString localCurrency(localeconv()->int_curr_symbol);
localCurrency.truncate(3);
QString baseCurrency;
try {
baseCurrency = MyMoneyFile::instance()->baseCurrency().id();
} catch (const MyMoneyException &e) {
qDebug("%s", qPrintable(e.what()));
}
// construct a transparent 16x16 pixmap
QPixmap empty(16, 16);
QBitmap mask(16, 16);
mask.clear();
empty.setMask(mask);
ui->m_currencyList->clear();
for (it = list.constBegin(); it != list.constEnd(); ++it) {
QTreeWidgetItem *p = new QTreeWidgetItem(ui->m_currencyList);
p->setText(0, (*it).name());
p->setData(0, Qt::UserRole, QVariant::fromValue(*it));
p->setFlags(p->flags() | Qt::ItemIsEditable);
p->setText(1, (*it).id());
p->setText(2, (*it).tradingSymbol());
if ((*it).id() == baseCurrency) {
p->setData(0, Qt::DecorationRole, QIcon::fromTheme(g_Icons[Icon::KMyMoney]));
if (m_currency.id().isEmpty())
first = p;
} else {
p->setData(0, Qt::DecorationRole, empty);
}
// if we had a previously selected
if (!m_currency.id().isEmpty()) {
if (m_currency.id() == p->text(1))
first = p;
} else if ((*it).id() == localCurrency && !first)
first = p;
}
ui->m_removeUnusedCurrencyButton->setDisabled(list.count() <= 1);
ui->m_currencyList->sortItems(0, Qt::AscendingOrder);
connect(ui->m_currencyList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(slotSelectCurrency(QTreeWidgetItem*)));
connect(ui->m_currencyList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotUpdateCurrency(QTreeWidgetItem*)));
if (first == 0)
first = ui->m_currencyList->invisibleRootItem()->child(0);
if (first != 0) {
ui->m_currencyList->setCurrentItem(first);
ui->m_currencyList->scrollToItem(first);
}
slotSelectCurrency(first);
}
void KCurrencyEditDlg::slotUpdateCurrency(QTreeWidgetItem* item)
{
//if there is no current item selected, exit
if (!ui->m_currencyList->currentItem() || item != ui->m_currencyList->currentItem())
return;
//verify that the stored currency id is not empty and the edited fields are not empty either
if (!m_currency.id().isEmpty()
&& !ui->m_currencyList->currentItem()->text(2).isEmpty()
&& !ui->m_currencyList->currentItem()->text(0).isEmpty()) {
//check that either the name or the id have changed
if (ui->m_currencyList->currentItem()->text(2) != m_currency.tradingSymbol()
|| ui->m_currencyList->currentItem()->text(0) != m_currency.name()) {
//update the name and the id
m_currency.setName(ui->m_currencyList->currentItem()->text(0));
m_currency.setTradingSymbol(ui->m_currencyList->currentItem()->text(2));
emit updateCurrency(m_currency.id(), m_currency.name(), m_currency.tradingSymbol());
}
}
}
void KCurrencyEditDlg::slotSelectCurrency(const QString& id)
{
QTreeWidgetItemIterator it(ui->m_currencyList);
while (*it) {
if ((*it)->text(1) == id) {
ui->m_currencyList->blockSignals(true);
slotSelectCurrency(*it);
ui->m_currencyList->setCurrentItem(*it);
ui->m_currencyList->scrollToItem(*it);
ui->m_currencyList->blockSignals(false);
break;
}
++it;
}
}
void KCurrencyEditDlg::slotSelectCurrency(QTreeWidgetItem *item)
{
MyMoneyFile* file = MyMoneyFile::instance();
QString baseId;
try {
baseId = MyMoneyFile::instance()->baseCurrency().id();
} catch (const MyMoneyException &) {
}
if (item) {
try {
m_currency = file->security(item->text(1));
} catch (const MyMoneyException &) {
m_currency = MyMoneySecurity();
}
QBitArray skip((int)eStorage::Reference::Count);
skip.fill(false);
skip.setBit((int)eStorage::Reference::Price);
const bool rc1 = m_currency.id() == baseId;
const bool rc2 = file->isReferenced(m_currency, skip);
const int count = ui->m_currencyList->selectedItems().count();
ui->m_selectBaseCurrencyButton->setDisabled(rc1 || count != 1);
ui->m_editCurrencyButton->setDisabled(count != 1);
ui->m_removeCurrencyButton->setDisabled((rc1 || rc2) && count <= 1);
emit selectObject(m_currency);
}
}
void KCurrencyEditDlg::slotItemSelectionChanged()
{
int count = ui->m_currencyList->selectedItems().count();
if (!ui->m_selectBaseCurrencyButton->isEnabled() && count == 1)
slotSelectCurrency(ui->m_currencyList->currentItem());
if (count > 1)
ui->m_removeCurrencyButton->setEnabled(true);
}
void KCurrencyEditDlg::slotStartRename()
{
QTreeWidgetItemIterator it_l(ui->m_currencyList, QTreeWidgetItemIterator::Selected);
QTreeWidgetItem* it_v;
if ((it_v = *it_l) != 0) {
ui->m_currencyList->editItem(it_v, 0);
}
}
void KCurrencyEditDlg::slotOpenContextMenu(const QPoint& p)
{
QTreeWidgetItem* item = ui->m_currencyList->itemAt(p);
if (item)
emit openContextMenu(item->data(0, Qt::UserRole).value<MyMoneySecurity>());
}
void KCurrencyEditDlg::slotSelectBaseCurrency()
{
if (!m_currency.id().isEmpty()) {
QTreeWidgetItem* p = ui->m_currencyList->currentItem();
emit selectBaseCurrency(m_currency);
// in case the dataChanged() signal was not sent out (nested FileTransaction)
// we update the list manually
if (p == ui->m_currencyList->currentItem())
slotLoadCurrencies();
}
}
void KCurrencyEditDlg::slotAddCurrency()
{
m_availableCurrencyDlg = new KAvailableCurrencyDlg; // create new dialog for selecting currencies to add
if (m_availableCurrencyDlg->exec() != QDialog::Rejected) {
MyMoneyFile* file = MyMoneyFile::instance();
QMap<MyMoneySecurity, MyMoneyPrice> ancientCurrencies = file->ancientCurrencies();
MyMoneyFileTransaction ft;
QList<QTreeWidgetItem *> currencyRows = m_availableCurrencyDlg->ui->m_currencyList->selectedItems(); // get selected currencies from new dialog
foreach (auto currencyRow, currencyRows) {
MyMoneySecurity currency = currencyRow->data(0, Qt::UserRole).value<MyMoneySecurity>();
file->addCurrency(currency);
if (ancientCurrencies.value(currency, MyMoneyPrice()) != MyMoneyPrice()) // if ancient currency is added...
file->addPrice(ancientCurrencies[currency]); // ...we want to add last known exchange rate as well
}
ft.commit();
ui->m_removeUnusedCurrencyButton->setDisabled(file->currencyList().count() <= 1);
}
delete m_availableCurrencyDlg;
}
void KCurrencyEditDlg::removeCurrency(const removalModeE& mode)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyFileTransaction ft;
QBitArray skip((int)eStorage::Reference::Count);
skip.fill(false); // check reference to all...
skip.setBit((int)eStorage::Reference::Price); // ...except price
QTreeWidgetItemIterator it (ui->m_currencyList); // iterate over whole tree
if (mode == RemoveUnused) {
while (*it) {
MyMoneySecurity currency = (*it)->data(0, Qt::UserRole).value<MyMoneySecurity>();
if (file->baseCurrency() != currency && !file->isReferenced(currency, skip))
KMyMoneyUtils::deleteSecurity(currency, this);
++it;
}
} else if (mode == RemoveSelected) {
QList<QTreeWidgetItem*> currencyRows = ui->m_currencyList->selectedItems();
foreach(auto currencyRow, currencyRows) {
MyMoneySecurity currency = currencyRow->data(0, Qt::UserRole).value<MyMoneySecurity>();
if (file->baseCurrency() != currency && !file->isReferenced(currency, skip))
KMyMoneyUtils::deleteSecurity(currency, this);
}
}
ft.commit();
ui->m_removeUnusedCurrencyButton->setDisabled(file->currencyList().count() <= 1);
}
void KCurrencyEditDlg::slotRemoveCurrency()
{
removeCurrency(RemoveSelected);
}
void KCurrencyEditDlg::slotRemoveUnusedCurrency()
{
removeCurrency(RemoveUnused);
}
void KCurrencyEditDlg::slotEditCurrency()
{
MyMoneySecurity currency = ui->m_currencyList->currentItem()->data(0, Qt::UserRole).value<MyMoneySecurity>();
m_currencyEditorDlg = new KCurrencyEditorDlg(currency); // create new dialog for editing currency
if (m_currencyEditorDlg->exec() != QDialog::Rejected) {
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyFileTransaction ft;
currency.setPricePrecision(m_currencyEditorDlg->ui->m_pricePrecision->value());
try {
file->modifyCurrency(currency);
ft.commit();
} catch (const MyMoneyException &e) {
qDebug("%s", qPrintable(e.what()));
}
}
delete m_currencyEditorDlg;
}
diff --git a/kmymoney/dialogs/keditscheduledlg.cpp b/kmymoney/dialogs/keditscheduledlg.cpp
index 41524b8d6..4cf366fb1 100644
--- a/kmymoney/dialogs/keditscheduledlg.cpp
+++ b/kmymoney/dialogs/keditscheduledlg.cpp
@@ -1,606 +1,608 @@
/***************************************************************************
keditscheduledlg.cpp - description
-------------------
begin : Mon Sep 3 2007
copyright : (C) 2007 by Thomas Baumgart
email : Thomas Baumgart <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 "keditscheduledlg.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QTimer>
#include <QCheckBox>
#include <QLabel>
#include <QList>
#include <QPushButton>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KStandardGuiItem>
#include <KLineEdit>
#include <KHelpClient>
#include <KGuiItem>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
#include "register.h"
#include "transactionform.h"
#include "transaction.h"
#include "transactioneditor.h"
#include "kmymoneylineedit.h"
#include "kmymoneydateinput.h"
#include "kmymoneymvccombo.h"
#include "kguiutils.h"
#include "kmymoney.h"
+using namespace eMyMoney;
+
class KEditScheduleDlg::Private
{
public:
MyMoneySchedule m_schedule;
KMyMoneyRegister::Transaction* m_item;
QWidgetList m_tabOrderWidgets;
TransactionEditor* m_editor;
kMandatoryFieldGroup* m_requiredFields;
};
KEditScheduleDlg::KEditScheduleDlg(const MyMoneySchedule& schedule, QWidget *parent) :
KEditScheduleDlgDecl(parent),
d(new Private)
{
setModal(true);
d->m_schedule = schedule;
d->m_editor = 0;
KGuiItem::assign(buttonOk, KStandardGuiItem::ok());
KGuiItem::assign(buttonCancel, KStandardGuiItem::cancel());
KGuiItem::assign(buttonHelp, KStandardGuiItem::help());
d->m_requiredFields = new kMandatoryFieldGroup(this);
d->m_requiredFields->setOkButton(buttonOk); // button to be enabled when all fields present
// make sure, we have a tabbar with the form
// insert it after the horizontal line
m_paymentInformationLayout->insertWidget(2, m_form->tabBar(m_form->parentWidget()));
// we never need to see the register
m_register->hide();
// ... setup the form ...
m_form->setupForm(d->m_schedule.account());
// ... and the register ...
m_register->clear();
// ... now add the transaction to register and form ...
MyMoneyTransaction t = transaction();
if (d->m_schedule.transaction().splits().isEmpty())
d->m_item = KMyMoneyRegister::Register::transactionFactory(m_register, t, MyMoneySplit(), 0);
else
d->m_item = KMyMoneyRegister::Register::transactionFactory(m_register, t,
d->m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : d->m_schedule.transaction().splits().front(), 0);
m_register->selectItem(d->m_item);
// show the account row
d->m_item->setShowRowInForm(0, true);
m_form->slotSetTransaction(d->m_item);
// setup widget contents
m_nameEdit->setText(d->m_schedule.name());
- m_frequencyEdit->setCurrentItem(d->m_schedule.occurrencePeriod());
- if (m_frequencyEdit->currentItem() == MyMoneySchedule::OCCUR_ANY)
- m_frequencyEdit->setCurrentItem(MyMoneySchedule::OCCUR_MONTHLY);
- slotFrequencyChanged(m_frequencyEdit->currentItem());
+ m_frequencyEdit->setCurrentItem((int)d->m_schedule.occurrencePeriod());
+ if (m_frequencyEdit->currentItem() == Schedule::Occurrence::Any)
+ m_frequencyEdit->setCurrentItem((int)Schedule::Occurrence::Monthly);
+ slotFrequencyChanged((int)m_frequencyEdit->currentItem());
m_frequencyNoEdit->setValue(d->m_schedule.occurrenceMultiplier());
// load option widgets
- m_paymentMethodEdit->insertItem(i18n("Direct deposit"), MyMoneySchedule::STYPE_DIRECTDEPOSIT);
- m_paymentMethodEdit->insertItem(i18n("Manual deposit"), MyMoneySchedule::STYPE_MANUALDEPOSIT);
- m_paymentMethodEdit->insertItem(i18n("Direct debit"), MyMoneySchedule::STYPE_DIRECTDEBIT);
- m_paymentMethodEdit->insertItem(i18n("Standing order"), MyMoneySchedule::STYPE_STANDINGORDER);
- m_paymentMethodEdit->insertItem(i18n("Bank transfer"), MyMoneySchedule::STYPE_BANKTRANSFER);
- m_paymentMethodEdit->insertItem(i18n("Write check"), MyMoneySchedule::STYPE_WRITECHEQUE);
- m_paymentMethodEdit->insertItem(i18nc("Other payment method", "Other"), MyMoneySchedule::STYPE_OTHER);
-
- MyMoneySchedule::paymentTypeE method = d->m_schedule.paymentType();
- if (method == MyMoneySchedule::STYPE_ANY)
- method = MyMoneySchedule::STYPE_OTHER;
- m_paymentMethodEdit->setCurrentItem(method);
+ m_paymentMethodEdit->insertItem(i18n("Direct deposit"), (int)Schedule::PaymentType::DirectDeposit);
+ m_paymentMethodEdit->insertItem(i18n("Manual deposit"), (int)Schedule::PaymentType::ManualDeposit);
+ m_paymentMethodEdit->insertItem(i18n("Direct debit"), (int)Schedule::PaymentType::DirectDebit);
+ m_paymentMethodEdit->insertItem(i18n("Standing order"), (int)Schedule::PaymentType::StandingOrder);
+ m_paymentMethodEdit->insertItem(i18n("Bank transfer"), (int)Schedule::PaymentType::BankTransfer);
+ m_paymentMethodEdit->insertItem(i18n("Write check"), (int)Schedule::PaymentType::WriteChecque);
+ m_paymentMethodEdit->insertItem(i18nc("Other payment method", "Other"), (int)Schedule::PaymentType::Other);
+
+ auto method = d->m_schedule.paymentType();
+ if (method == Schedule::PaymentType::Any)
+ method = Schedule::PaymentType::Other;
+ m_paymentMethodEdit->setCurrentItem((int)method);
switch (d->m_schedule.weekendOption()) {
- case MyMoneySchedule::MoveNothing:
+ case Schedule::WeekendOption::MoveNothing:
m_weekendOptionEdit->setCurrentIndex(0);
break;
- case MyMoneySchedule::MoveBefore:
+ case Schedule::WeekendOption::MoveBefore:
m_weekendOptionEdit->setCurrentIndex(1);
break;
- case MyMoneySchedule::MoveAfter:
+ case Schedule::WeekendOption::MoveAfter:
m_weekendOptionEdit->setCurrentIndex(2);
break;
}
m_estimateEdit->setChecked(!d->m_schedule.isFixed());
m_autoEnterEdit->setChecked(d->m_schedule.autoEnter());
m_endSeriesEdit->setChecked(d->m_schedule.willEnd());
m_endOptionsFrame->setEnabled(d->m_schedule.willEnd());
if (d->m_schedule.willEnd()) {
m_RemainingEdit->setValue(d->m_schedule.transactionsRemaining());
m_FinalPaymentEdit->setDate(d->m_schedule.endDate());
}
connect(m_RemainingEdit, SIGNAL(valueChanged(int)),
this, SLOT(slotRemainingChanged(int)));
connect(m_FinalPaymentEdit, SIGNAL(dateChanged(QDate)),
this, SLOT(slotEndDateChanged(QDate)));
connect(m_frequencyEdit, SIGNAL(itemSelected(int)),
this, SLOT(slotFrequencyChanged(int)));
connect(m_frequencyNoEdit, SIGNAL(valueChanged(int)),
this, SLOT(slotOccurrenceMultiplierChanged(int)));
connect(buttonHelp, SIGNAL(clicked()), this, SLOT(slotShowHelp()));
// force the initial height to be as small as possible
QTimer::singleShot(0, this, SLOT(slotSetupSize()));
// we just hide the variation field for now and enable the logic
// once we have a respective member in the MyMoneySchedule object
m_variation->hide();
}
KEditScheduleDlg::~KEditScheduleDlg()
{
delete d;
}
void KEditScheduleDlg::slotSetupSize()
{
resize(width(), minimumSizeHint().height());
}
TransactionEditor* KEditScheduleDlg::startEdit()
{
KMyMoneyRegister::SelectedTransactions list(m_register);
TransactionEditor* editor = d->m_item->createEditor(m_form, list, QDate());
// check that we use the same transaction commodity in all selected transactions
// if not, we need to update this in the editor's list. The user can also bail out
// of this operation which means that we have to stop editing here.
if (editor && !d->m_schedule.account().id().isEmpty()) {
if (!editor->fixTransactionCommodity(d->m_schedule.account())) {
// if the user wants to quit, we need to destroy the editor
// and bail out
delete editor;
editor = 0;
}
}
if (editor) {
editor->m_scheduleInfo = m_nameEdit->text();
connect(editor, SIGNAL(transactionDataSufficient(bool)), buttonOk, SLOT(setEnabled(bool)));
connect(editor, SIGNAL(escapePressed()), buttonCancel, SLOT(animateClick()));
connect(editor, SIGNAL(returnPressed()), buttonOk, SLOT(animateClick()));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), editor, SLOT(slotReloadEditWidgets()));
// connect(editor, SIGNAL(finishEdit(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotLeaveEditMode(KMyMoneyRegister::SelectedTransactions)));
connect(editor, SIGNAL(createPayee(QString,QString&)), kmymoney, SLOT(slotPayeeNew(QString,QString&)));
connect(editor, SIGNAL(createTag(QString,QString&)), kmymoney, SLOT(slotTagNew(QString,QString&)));
connect(editor, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount)));
connect(editor, SIGNAL(createSecurity(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotInvestmentNew(MyMoneyAccount&,MyMoneyAccount)));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), editor, SLOT(slotReloadEditWidgets()));
// create the widgets, place them in the parent and load them with data
// setup tab order
d->m_tabOrderWidgets.clear();
KMyMoneyRegister::Action action = KMyMoneyRegister::ActionWithdrawal;
switch (d->m_schedule.type()) {
- case MyMoneySchedule::TYPE_DEPOSIT:
+ case Schedule::Type::Deposit:
action = KMyMoneyRegister::ActionDeposit;
break;
- case MyMoneySchedule::TYPE_BILL:
+ case Schedule::Type::Bill:
action = KMyMoneyRegister::ActionWithdrawal;
editor->m_paymentMethod = d->m_schedule.paymentType();
break;
- case MyMoneySchedule::TYPE_TRANSFER:
+ case Schedule::Type::Transfer:
action = KMyMoneyRegister::ActionTransfer;
break;
default:
// if we end up here, we don't have a known schedule type (yet). in this case, we just glimpse
// into the transaction and determine the type. in case we don't have a transaction with splits
// we stick with the default action already set up
if (d->m_schedule.transaction().splits().count() > 0) {
QList<MyMoneySplit>::const_iterator it_s;
bool isDeposit = false;
bool isTransfer = false;
for (it_s = d->m_schedule.transaction().splits().begin(); it_s != d->m_schedule.transaction().splits().end(); ++it_s) {
if ((*it_s).accountId() == d->m_schedule.account().id()) {
isDeposit = !((*it_s).shares().isNegative());
} else {
MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
if (acc.isAssetLiability() && d->m_schedule.transaction().splits().count() == 2) {
isTransfer = true;
}
}
}
if (isTransfer)
action = KMyMoneyRegister::ActionTransfer;
else if (isDeposit)
action = KMyMoneyRegister::ActionDeposit;
}
break;
}
editor->setup(d->m_tabOrderWidgets, d->m_schedule.account(), action);
// if it's not a check, then we need to clear
// a possibly assigned check number
- if (d->m_schedule.paymentType() != MyMoneySchedule::STYPE_WRITECHEQUE) {
+ if (d->m_schedule.paymentType() != Schedule::PaymentType::WriteChecque) {
QWidget* w = editor->haveWidget("number");
if (w)
dynamic_cast<kMyMoneyLineEdit*>(w)->loadText(QString());
}
Q_ASSERT(!d->m_tabOrderWidgets.isEmpty());
d->m_tabOrderWidgets.push_front(m_paymentMethodEdit);
// editor->setup() leaves the tabbar as the last widget in the stack, but we
// need it as first here. So we move it around.
QWidget* w = editor->haveWidget("tabbar");
if (w) {
int idx = d->m_tabOrderWidgets.indexOf(w);
if (idx != -1) {
d->m_tabOrderWidgets.removeAt(idx);
d->m_tabOrderWidgets.push_front(w);
}
}
// don't forget our three buttons and additional widgets
// make sure to use the correct order
d->m_tabOrderWidgets.push_front(m_frequencyEdit);
d->m_tabOrderWidgets.push_front(m_frequencyNoEdit);
d->m_tabOrderWidgets.push_front(m_nameEdit);
d->m_tabOrderWidgets.append(m_weekendOptionEdit);
d->m_tabOrderWidgets.append(m_estimateEdit);
d->m_tabOrderWidgets.append(m_variation);
d->m_tabOrderWidgets.append(m_autoEnterEdit);
d->m_tabOrderWidgets.append(m_endSeriesEdit);
d->m_tabOrderWidgets.append(m_RemainingEdit);
d->m_tabOrderWidgets.append(m_FinalPaymentEdit);
d->m_tabOrderWidgets.append(buttonOk);
d->m_tabOrderWidgets.append(buttonCancel);
d->m_tabOrderWidgets.append(buttonHelp);
for (int i = 0; i < d->m_tabOrderWidgets.size(); ++i) {
QWidget* w = d->m_tabOrderWidgets.at(i);
if (w) {
w->installEventFilter(this);
w->installEventFilter(editor);
}
}
// connect the postdate modification signal to our update routine
kMyMoneyDateInput* dateEdit = dynamic_cast<kMyMoneyDateInput*>(editor->haveWidget("postdate"));
if (dateEdit)
connect(dateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(slotPostDateChanged(QDate)));
m_nameEdit->setFocus();
// add the required fields to the mandatory group
d->m_requiredFields->add(m_nameEdit);
d->m_requiredFields->add(editor->haveWidget("account"));
d->m_requiredFields->add(editor->haveWidget("category"));
d->m_requiredFields->add(editor->haveWidget("amount"));
// fix labels
QLabel* label = dynamic_cast<QLabel*>(editor->haveWidget("date-label"));
if (label) {
label->setText(i18n("Next due date"));
}
d->m_editor = editor;
- slotSetPaymentMethod(d->m_schedule.paymentType());
+ slotSetPaymentMethod((int)d->m_schedule.paymentType());
connect(m_paymentMethodEdit, SIGNAL(itemSelected(int)), this, SLOT(slotSetPaymentMethod(int)));
connect(editor, SIGNAL(operationTypeChanged(int)), this, SLOT(slotFilterPaymentType(int)));
}
return editor;
}
void KEditScheduleDlg::accept()
{
// Force the focus to be on the OK button. This will trigger creation
// of any unknown objects (payees, categories etc.)
buttonOk->setFocus();
// only accept if the button is really still enabled. We could end
// up here, if the user filled all fields, the focus is on the category
// field, but the category is not yet existent. When the user presses the
// OK button in this context, he will be asked if he wants to create
// the category or not. In case he decides no, we end up here with no
// category filled in, so we don't run through the final acceptance.
if (buttonOk->isEnabled())
KEditScheduleDlgDecl::accept();
}
const MyMoneySchedule& KEditScheduleDlg::schedule() const
{
if (d->m_editor) {
MyMoneyTransaction t = transaction();
if (d->m_schedule.nextDueDate() != t.postDate()) {
d->m_schedule.setNextDueDate(t.postDate());
d->m_schedule.setStartDate(t.postDate());
}
d->m_schedule.setTransaction(t);
d->m_schedule.setName(m_nameEdit->text());
d->m_schedule.setFixed(!m_estimateEdit->isChecked());
- d->m_schedule.setOccurrencePeriod(static_cast<MyMoneySchedule::occurrenceE>(m_frequencyEdit->currentItem()));
+ d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(m_frequencyEdit->currentItem()));
d->m_schedule.setOccurrenceMultiplier(m_frequencyNoEdit->value());
switch (m_weekendOptionEdit->currentIndex()) {
case 0:
- d->m_schedule.setWeekendOption(MyMoneySchedule::MoveNothing);
+ d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveNothing);
break;
case 1:
- d->m_schedule.setWeekendOption(MyMoneySchedule::MoveBefore);
+ d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveBefore);
break;
case 2:
- d->m_schedule.setWeekendOption(MyMoneySchedule::MoveAfter);
+ d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveAfter);
break;
}
- d->m_schedule.setType(MyMoneySchedule::TYPE_BILL);
+ d->m_schedule.setType(Schedule::Type::Bill);
KMyMoneyTransactionForm::TabBar* tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(d->m_editor->haveWidget("tabbar"));
if (tabbar) {
switch (static_cast<KMyMoneyRegister::Action>(tabbar->currentIndex())) {
case KMyMoneyRegister::ActionDeposit:
- d->m_schedule.setType(MyMoneySchedule::TYPE_DEPOSIT);
+ d->m_schedule.setType(Schedule::Type::Deposit);
break;
default:
case KMyMoneyRegister::ActionWithdrawal:
- d->m_schedule.setType(MyMoneySchedule::TYPE_BILL);
+ d->m_schedule.setType(Schedule::Type::Bill);
break;
case KMyMoneyRegister::ActionTransfer:
- d->m_schedule.setType(MyMoneySchedule::TYPE_TRANSFER);
+ d->m_schedule.setType(Schedule::Type::Transfer);
break;
}
} else {
qDebug("No tabbar found in KEditScheduleDlg::schedule(). Defaulting type to BILL");
}
d->m_schedule.setAutoEnter(m_autoEnterEdit->isChecked());
- d->m_schedule.setPaymentType(static_cast<MyMoneySchedule::paymentTypeE>(m_paymentMethodEdit->currentItem()));
+ d->m_schedule.setPaymentType(static_cast<Schedule::PaymentType>(m_paymentMethodEdit->currentItem()));
if (m_endSeriesEdit->isEnabled() && m_endSeriesEdit->isChecked()) {
d->m_schedule.setEndDate(m_FinalPaymentEdit->date());
} else {
d->m_schedule.setEndDate(QDate());
}
}
return d->m_schedule;
}
MyMoneyTransaction KEditScheduleDlg::transaction() const
{
MyMoneyTransaction t = d->m_schedule.transaction();
if (d->m_editor) {
d->m_editor->createTransaction(t, d->m_schedule.transaction(), d->m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : d->m_schedule.transaction().splits().front(), false);
}
t.clearId();
t.setEntryDate(QDate());
return t;
}
bool KEditScheduleDlg::focusNextPrevChild(bool next)
{
bool rc = false;
QWidget *w = 0;
w = qApp->focusWidget();
int currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w);
while (w && currentWidgetIndex == -1) {
// qDebug("'%s' not in list, use parent", qPrintable(w->objectName()));
w = w->parentWidget();
currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w);
}
if (currentWidgetIndex != -1) {
do {
// if(w) qDebug("tab order is at '%s (%d/%d)'", qPrintable(w->objectName()), currentWidgetIndex, d->m_tabOrderWidgets.size());
currentWidgetIndex += next ? 1 : -1;
if (currentWidgetIndex < 0)
currentWidgetIndex = d->m_tabOrderWidgets.size() - 1;
else if (currentWidgetIndex >= d->m_tabOrderWidgets.size())
currentWidgetIndex = 0;
w = d->m_tabOrderWidgets[currentWidgetIndex];
// qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w);
if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) {
// qDebug("Selecting '%s' as focus", qPrintable(w->objectName()));
w->setFocus();
rc = true;
}
} while (rc == false);
}
return rc;
}
void KEditScheduleDlg::resizeEvent(QResizeEvent* ev)
{
m_register->resize(KMyMoneyRegister::DetailColumn);
m_form->resize(KMyMoneyTransactionForm::ValueColumn1);
KEditScheduleDlgDecl::resizeEvent(ev);
}
void KEditScheduleDlg::slotRemainingChanged(int value)
{
// Make sure the required fields are set
kMyMoneyDateInput* dateEdit = dynamic_cast<kMyMoneyDateInput*>(d->m_editor->haveWidget("postdate"));
d->m_schedule.setNextDueDate(dateEdit->date());
- d->m_schedule.setOccurrencePeriod(static_cast<MyMoneySchedule::occurrenceE>(m_frequencyEdit->currentItem()));
+ d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(m_frequencyEdit->currentItem()));
d->m_schedule.setOccurrenceMultiplier(m_frequencyNoEdit->value());
if (d->m_schedule.transactionsRemaining() != value) {
m_FinalPaymentEdit->blockSignals(true);
m_FinalPaymentEdit->setDate(d->m_schedule.dateAfter(value));
m_FinalPaymentEdit->blockSignals(false);
}
}
void KEditScheduleDlg::slotEndDateChanged(const QDate& date)
{
// Make sure the required fields are set
kMyMoneyDateInput* dateEdit = dynamic_cast<kMyMoneyDateInput*>(d->m_editor->haveWidget("postdate"));
d->m_schedule.setNextDueDate(dateEdit->date());
- d->m_schedule.setOccurrencePeriod(static_cast<MyMoneySchedule::occurrenceE>(m_frequencyEdit->currentItem()));
+ d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(m_frequencyEdit->currentItem()));
d->m_schedule.setOccurrenceMultiplier(m_frequencyNoEdit->value());
if (d->m_schedule.endDate() != date) {
d->m_schedule.setEndDate(date);
updateTransactionsRemaining();
}
}
void KEditScheduleDlg::slotPostDateChanged(const QDate& date)
{
if (d->m_schedule.nextDueDate() != date) {
if (m_endOptionsFrame->isEnabled()) {
d->m_schedule.setNextDueDate(date);
d->m_schedule.setOccurrenceMultiplier(m_frequencyNoEdit->value());
- d->m_schedule.setOccurrencePeriod(static_cast<MyMoneySchedule::occurrenceE>(m_frequencyEdit->currentItem()));
+ d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(m_frequencyEdit->currentItem()));
d->m_schedule.setEndDate(m_FinalPaymentEdit->date());
updateTransactionsRemaining();
}
}
}
void KEditScheduleDlg::slotSetPaymentMethod(int item)
{
kMyMoneyLineEdit* dateEdit = dynamic_cast<kMyMoneyLineEdit*>(d->m_editor->haveWidget("number"));
if (dateEdit) {
- dateEdit->setVisible(item == MyMoneySchedule::STYPE_WRITECHEQUE);
+ dateEdit->setVisible(item == (int)Schedule::PaymentType::WriteChecque);
// hiding the label does not work, because the label underneath will shine
// through. So we either write the label or a blank
QLabel* label = dynamic_cast<QLabel *>(d->m_editor->haveWidget("number-label"));
if (label) {
- label->setText((item == MyMoneySchedule::STYPE_WRITECHEQUE) ? i18n("Number") : " ");
+ label->setText((item == (int)Schedule::PaymentType::WriteChecque) ? i18n("Number") : " ");
}
}
}
void KEditScheduleDlg::slotFrequencyChanged(int item)
{
- m_endSeriesEdit->setEnabled(item != MyMoneySchedule::OCCUR_ONCE);
+ m_endSeriesEdit->setEnabled(item != (int)Schedule::Occurrence::Once);
bool isEndSeries = m_endSeriesEdit->isChecked();
if (isEndSeries)
- m_endOptionsFrame->setEnabled(item != MyMoneySchedule::OCCUR_ONCE);
+ m_endOptionsFrame->setEnabled(item != (int)Schedule::Occurrence::Once);
switch (item) {
- case MyMoneySchedule::OCCUR_DAILY:
- case MyMoneySchedule::OCCUR_WEEKLY:
- case MyMoneySchedule::OCCUR_EVERYHALFMONTH:
- case MyMoneySchedule::OCCUR_MONTHLY:
- case MyMoneySchedule::OCCUR_YEARLY:
+ case (int)Schedule::Occurrence::Daily:
+ case (int)Schedule::Occurrence::Weekly:
+ case (int)Schedule::Occurrence::EveryHalfMonth:
+ case (int)Schedule::Occurrence::Monthly:
+ case (int)Schedule::Occurrence::Yearly:
// Supports Frequency Number
m_frequencyNoEdit->setEnabled(true);
break;
default:
// Multiplier is always 1
m_frequencyNoEdit->setEnabled(false);
m_frequencyNoEdit->setValue(1);
break;
}
- if (isEndSeries && (item != MyMoneySchedule::OCCUR_ONCE)) {
+ if (isEndSeries && (item != (int)Schedule::Occurrence::Once)) {
// Changing the frequency changes the number
// of remaining transactions
kMyMoneyDateInput* dateEdit = dynamic_cast<kMyMoneyDateInput*>(d->m_editor->haveWidget("postdate"));
d->m_schedule.setNextDueDate(dateEdit->date());
d->m_schedule.setOccurrenceMultiplier(m_frequencyNoEdit->value());
- d->m_schedule.setOccurrencePeriod(static_cast<MyMoneySchedule::occurrenceE>(item));
+ d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(item));
d->m_schedule.setEndDate(m_FinalPaymentEdit->date());
updateTransactionsRemaining();
}
}
void KEditScheduleDlg::slotOccurrenceMultiplierChanged(int multiplier)
{
// Make sure the required fields are set
int oldOccurrenceMultiplier = d->m_schedule.occurrenceMultiplier();
if (multiplier != oldOccurrenceMultiplier) {
if (m_endOptionsFrame->isEnabled()) {
kMyMoneyDateInput* dateEdit = dynamic_cast<kMyMoneyDateInput*>(d->m_editor->haveWidget("postdate"));
d->m_schedule.setNextDueDate(dateEdit->date());
d->m_schedule.setOccurrenceMultiplier(multiplier);
- d->m_schedule.setOccurrencePeriod(static_cast<MyMoneySchedule::occurrenceE>(m_frequencyEdit->currentItem()));
+ d->m_schedule.setOccurrencePeriod(static_cast<Schedule::Occurrence>(m_frequencyEdit->currentItem()));
d->m_schedule.setEndDate(m_FinalPaymentEdit->date());
updateTransactionsRemaining();
}
}
}
void KEditScheduleDlg::updateTransactionsRemaining()
{
int remain = d->m_schedule.transactionsRemaining();
if (remain != m_RemainingEdit->value()) {
m_RemainingEdit->blockSignals(true);
m_RemainingEdit->setValue(remain);
m_RemainingEdit->blockSignals(false);
}
}
void KEditScheduleDlg::slotShowHelp()
{
KHelpClient::invokeHelp("details.schedules.intro");
}
void KEditScheduleDlg::slotFilterPaymentType(int index)
{
//save selected item to reload if possible
int selectedId = m_paymentMethodEdit->itemData(m_paymentMethodEdit->currentIndex(), Qt::UserRole).toInt();
//clear and reload the widget with the correct items
m_paymentMethodEdit->clear();
// load option widgets
KMyMoneyRegister::Action action = static_cast<KMyMoneyRegister::Action>(index);
if (action != KMyMoneyRegister::ActionWithdrawal) {
- m_paymentMethodEdit->insertItem(i18n("Direct deposit"), MyMoneySchedule::STYPE_DIRECTDEPOSIT);
- m_paymentMethodEdit->insertItem(i18n("Manual deposit"), MyMoneySchedule::STYPE_MANUALDEPOSIT);
+ m_paymentMethodEdit->insertItem(i18n("Direct deposit"), (int)Schedule::PaymentType::DirectDeposit);
+ m_paymentMethodEdit->insertItem(i18n("Manual deposit"), (int)Schedule::PaymentType::ManualDeposit);
}
if (action != KMyMoneyRegister::ActionDeposit) {
- m_paymentMethodEdit->insertItem(i18n("Direct debit"), MyMoneySchedule::STYPE_DIRECTDEBIT);
- m_paymentMethodEdit->insertItem(i18n("Write check"), MyMoneySchedule::STYPE_WRITECHEQUE);
+ m_paymentMethodEdit->insertItem(i18n("Direct debit"), (int)Schedule::PaymentType::DirectDebit);
+ m_paymentMethodEdit->insertItem(i18n("Write check"), (int)Schedule::PaymentType::WriteChecque);
}
- m_paymentMethodEdit->insertItem(i18n("Standing order"), MyMoneySchedule::STYPE_STANDINGORDER);
- m_paymentMethodEdit->insertItem(i18n("Bank transfer"), MyMoneySchedule::STYPE_BANKTRANSFER);
- m_paymentMethodEdit->insertItem(i18nc("Other payment method", "Other"), MyMoneySchedule::STYPE_OTHER);
+ m_paymentMethodEdit->insertItem(i18n("Standing order"), (int)Schedule::PaymentType::StandingOrder);
+ m_paymentMethodEdit->insertItem(i18n("Bank transfer"), (int)Schedule::PaymentType::BankTransfer);
+ m_paymentMethodEdit->insertItem(i18nc("Other payment method", "Other"), (int)Schedule::PaymentType::Other);
int newIndex = m_paymentMethodEdit->findData(QVariant(selectedId), Qt::UserRole, Qt::MatchExactly);
if (newIndex > -1) {
m_paymentMethodEdit->setCurrentIndex(newIndex);
} else {
m_paymentMethodEdit->setCurrentIndex(0);
}
}
diff --git a/kmymoney/dialogs/kenterscheduledlg.cpp b/kmymoney/dialogs/kenterscheduledlg.cpp
index 8c9b9624f..f46baef19 100644
--- a/kmymoney/dialogs/kenterscheduledlg.cpp
+++ b/kmymoney/dialogs/kenterscheduledlg.cpp
@@ -1,351 +1,351 @@
/***************************************************************************
kenterscheduledlg.cpp
-------------------
begin : Sat Apr 7 2007
copyright : (C) 2007 by Thomas Baumgart
email : Thomas Baumgart <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 "kenterscheduledlg.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QTimer>
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KMessageBox>
#include <KHelpClient>
#include <KLocalizedString>
#include <KGuiItem>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
#include "register.h"
#include "transactionform.h"
#include "transaction.h"
#include "transactioneditor.h"
#include "kmymoneyutils.h"
#include "kmymoneylineedit.h"
#include "kmymoneydateinput.h"
#include <KStandardGuiItem>
#include "kmymoney.h"
#include "icons/icons.h"
using namespace Icons;
class KEnterScheduleDlg::Private
{
public:
Private() : m_item(0), m_showWarningOnce(true) {}
~Private() {}
MyMoneySchedule m_schedule;
KMyMoneyRegister::Transaction* m_item;
QWidgetList m_tabOrderWidgets;
bool m_showWarningOnce;
KMyMoneyUtils::EnterScheduleResultCodeE m_extendedReturnCode;
};
KEnterScheduleDlg::KEnterScheduleDlg(QWidget *parent, const MyMoneySchedule& schedule) :
KEnterScheduleDlgDecl(parent),
d(new Private)
{
d->m_schedule = schedule;
d->m_extendedReturnCode = KMyMoneyUtils::Enter;
buttonOk->setIcon(QIcon::fromTheme(g_Icons[Icon::KeyEnter]));
buttonSkip->setIcon(QIcon::fromTheme(g_Icons[Icon::MediaSeekForward]));
KGuiItem::assign(buttonCancel, KStandardGuiItem::cancel());
KGuiItem::assign(buttonHelp, KStandardGuiItem::help());
buttonIgnore->setHidden(true);
buttonSkip->setHidden(true);
// make sure, we have a tabbar with the form
KMyMoneyTransactionForm::TabBar* tabbar = m_form->tabBar(m_form->parentWidget());
// we never need to see the register
m_register->hide();
// ... setup the form ...
m_form->setupForm(d->m_schedule.account());
// ... and the register ...
m_register->clear();
// ... now add the transaction to register and form ...
MyMoneyTransaction t = transaction();
d->m_item = KMyMoneyRegister::Register::transactionFactory(m_register, t,
d->m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : d->m_schedule.transaction().splits().front(), 0);
m_register->selectItem(d->m_item);
// show the account row
d->m_item->setShowRowInForm(0, true);
m_form->slotSetTransaction(d->m_item);
// no need to see the tabbar
tabbar->hide();
// setup name and type
m_scheduleName->setText(d->m_schedule.name());
m_type->setText(KMyMoneyUtils::scheduleTypeToString(d->m_schedule.type()));
connect(buttonHelp, SIGNAL(clicked()), this, SLOT(slotShowHelp()));
connect(buttonIgnore, SIGNAL(clicked()), this, SLOT(slotIgnore()));
connect(buttonSkip, SIGNAL(clicked()), this, SLOT(slotSkip()));
}
KEnterScheduleDlg::~KEnterScheduleDlg()
{
delete d;
}
KMyMoneyUtils::EnterScheduleResultCodeE KEnterScheduleDlg::resultCode() const
{
if (result() == QDialog::Accepted)
return d->m_extendedReturnCode;
return KMyMoneyUtils::Cancel;
}
void KEnterScheduleDlg::showExtendedKeys(bool visible)
{
buttonIgnore->setVisible(visible);
buttonSkip->setVisible(visible);
}
void KEnterScheduleDlg::slotIgnore()
{
d->m_extendedReturnCode = KMyMoneyUtils::Ignore;
accept();
}
void KEnterScheduleDlg::slotSkip()
{
d->m_extendedReturnCode = KMyMoneyUtils::Skip;
accept();
}
MyMoneyTransaction KEnterScheduleDlg::transaction()
{
MyMoneyTransaction t = d->m_schedule.transaction();
try {
- if (d->m_schedule.type() == MyMoneySchedule::TYPE_LOANPAYMENT) {
+ if (d->m_schedule.type() == eMyMoney::Schedule::Type::LoanPayment) {
KMyMoneyUtils::calculateAutoLoan(d->m_schedule, t, QMap<QString, MyMoneyMoney>());
}
} catch (const MyMoneyException &e) {
KMessageBox::detailedError(this, i18n("Unable to load schedule details"), e.what());
}
t.clearId();
t.setEntryDate(QDate());
return t;
}
QDate KEnterScheduleDlg::date(const QDate& _date) const
{
QDate date(_date);
return d->m_schedule.adjustedDate(date, d->m_schedule.weekendOption());
}
void KEnterScheduleDlg::resizeEvent(QResizeEvent* ev)
{
m_register->resize(KMyMoneyRegister::DetailColumn);
m_form->resize(KMyMoneyTransactionForm::ValueColumn1);
KEnterScheduleDlgDecl::resizeEvent(ev);
}
void KEnterScheduleDlg::slotSetupSize()
{
resize(width(), minimumSizeHint().height());
}
int KEnterScheduleDlg::exec()
{
if (d->m_showWarningOnce) {
d->m_showWarningOnce = false;
KMessageBox::information(this, QString("<qt>") + i18n("<p>Please check that all the details in the following dialog are correct and press OK.</p><p>Editable data can be changed and can either be applied to just this occurrence or for all subsequent occurrences for this schedule. (You will be asked what you intend after pressing OK in the following dialog)</p>") + QString("</qt>"), i18n("Enter scheduled transaction"), "EnterScheduleDlgInfo");
}
// force the initial height to be as small as possible
QTimer::singleShot(0, this, SLOT(slotSetupSize()));
return KEnterScheduleDlgDecl::exec();
}
TransactionEditor* KEnterScheduleDlg::startEdit()
{
KMyMoneyRegister::SelectedTransactions list(m_register);
TransactionEditor* editor = d->m_item->createEditor(m_form, list, QDate());
editor->m_scheduleInfo = d->m_schedule.name();
editor->m_paymentMethod = d->m_schedule.paymentType();
// check that we use the same transaction commodity in all selected transactions
// if not, we need to update this in the editor's list. The user can also bail out
// of this operation which means that we have to stop editing here.
if (editor) {
if (!editor->fixTransactionCommodity(d->m_schedule.account())) {
// if the user wants to quit, we need to destroy the editor
// and bail out
delete editor;
editor = 0;
}
}
if (editor) {
connect(editor, SIGNAL(transactionDataSufficient(bool)), buttonOk, SLOT(setEnabled(bool)));
connect(editor, SIGNAL(escapePressed()), buttonCancel, SLOT(animateClick()));
connect(editor, SIGNAL(returnPressed()), buttonOk, SLOT(animateClick()));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), editor, SLOT(slotReloadEditWidgets()));
// connect(editor, SIGNAL(finishEdit(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotLeaveEditMode(KMyMoneyRegister::SelectedTransactions)));
connect(editor, SIGNAL(createPayee(QString,QString&)), kmymoney, SLOT(slotPayeeNew(QString,QString&)));
connect(editor, SIGNAL(createTag(QString,QString&)), kmymoney, SLOT(slotTagNew(QString,QString&)));
connect(editor, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount)));
connect(editor, SIGNAL(createSecurity(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotInvestmentNew(MyMoneyAccount&,MyMoneyAccount)));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), editor, SLOT(slotReloadEditWidgets()));
// create the widgets, place them in the parent and load them with data
// setup tab order
d->m_tabOrderWidgets.clear();
KMyMoneyRegister::Action action = KMyMoneyRegister::ActionWithdrawal;
switch (d->m_schedule.type()) {
- case MyMoneySchedule::TYPE_TRANSFER:
+ case eMyMoney::Schedule::Type::Transfer:
action = KMyMoneyRegister::ActionTransfer;
break;
- case MyMoneySchedule::TYPE_DEPOSIT:
+ case eMyMoney::Schedule::Type::Deposit:
action = KMyMoneyRegister::ActionDeposit;
break;
- case MyMoneySchedule::TYPE_LOANPAYMENT:
+ case eMyMoney::Schedule::Type::LoanPayment:
switch (d->m_schedule.paymentType()) {
- case MyMoneySchedule::STYPE_DIRECTDEPOSIT:
- case MyMoneySchedule::STYPE_MANUALDEPOSIT:
+ case eMyMoney::Schedule::PaymentType::DirectDeposit:
+ case eMyMoney::Schedule::PaymentType::ManualDeposit:
action = KMyMoneyRegister::ActionDeposit;
break;
default:
break;
}
break;
default:
break;
}
editor->setup(d->m_tabOrderWidgets, d->m_schedule.account(), action);
MyMoneyTransaction t = d->m_schedule.transaction();
QString num = t.splits().first().number();
QWidget* w = editor->haveWidget("number");
- if (d->m_schedule.paymentType() == MyMoneySchedule::STYPE_WRITECHEQUE) {
+ if (d->m_schedule.paymentType() == eMyMoney::Schedule::PaymentType::WriteChecque) {
MyMoneyFile* file = MyMoneyFile::instance();
if (file->checkNoUsed(d->m_schedule.account().id(), num)) {
// increment and try again
num = KMyMoneyUtils::getAdjacentNumber(num);
}
num = KMyMoneyUtils::nextCheckNumber(d->m_schedule.account());
KMyMoneyUtils::updateLastNumberUsed(d->m_schedule.account(), num);
d->m_schedule.account().setValue("lastNumberUsed", num);
if (w) {
dynamic_cast<kMyMoneyLineEdit*>(w)->loadText(num);
}
} else {
// if it's not a check, then we need to clear
// a possibly assigned check number
if (w)
dynamic_cast<kMyMoneyLineEdit*>(w)->loadText(QString());
}
Q_ASSERT(!d->m_tabOrderWidgets.isEmpty());
// editor->setup() leaves the tabbar as the last widget in the stack, but we
// need it as first here. So we move it around.
w = editor->haveWidget("tabbar");
if (w) {
int idx = d->m_tabOrderWidgets.indexOf(w);
if (idx != -1) {
d->m_tabOrderWidgets.removeAt(idx);
d->m_tabOrderWidgets.push_front(w);
}
}
// don't forget our three buttons
d->m_tabOrderWidgets.append(buttonOk);
d->m_tabOrderWidgets.append(buttonCancel);
d->m_tabOrderWidgets.append(buttonHelp);
for (int i = 0; i < d->m_tabOrderWidgets.size(); ++i) {
QWidget* w = d->m_tabOrderWidgets.at(i);
if (w) {
w->installEventFilter(this);
w->installEventFilter(editor);
}
}
// Check if the editor has some preference on where to set the focus
// If not, set the focus to the first widget in the tab order
QWidget* focusWidget = editor->firstWidget();
if (!focusWidget)
focusWidget = d->m_tabOrderWidgets.first();
focusWidget->setFocus();
// Make sure, we use the adjusted date
kMyMoneyDateInput* dateEdit = dynamic_cast<kMyMoneyDateInput*>(editor->haveWidget("postdate"));
if (dateEdit) {
dateEdit->setDate(d->m_schedule.adjustedNextDueDate());
}
}
return editor;
}
bool KEnterScheduleDlg::focusNextPrevChild(bool next)
{
bool rc = false;
QWidget *w = 0;
w = qApp->focusWidget();
int currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w);
while (w && currentWidgetIndex == -1) {
// qDebug("'%s' not in list, use parent", w->className());
w = w->parentWidget();
currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w);
}
if (currentWidgetIndex != -1) {
// if(w) qDebug("tab order is at '%s'", w->className());
currentWidgetIndex += next ? 1 : -1;
if (currentWidgetIndex < 0)
currentWidgetIndex = d->m_tabOrderWidgets.size() - 1;
else if (currentWidgetIndex >= d->m_tabOrderWidgets.size())
currentWidgetIndex = 0;
w = d->m_tabOrderWidgets[currentWidgetIndex];
// qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w);
if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) {
// qDebug("Selecting '%s' as focus", w->className());
w->setFocus();
rc = true;
}
}
return rc;
}
void KEnterScheduleDlg::slotShowHelp()
{
KHelpClient::invokeHelp("details.schedules.entering");
}
diff --git a/kmymoney/dialogs/kfindtransactiondlg.cpp b/kmymoney/dialogs/kfindtransactiondlg.cpp
index c64d5bab1..fbee12668 100644
--- a/kmymoney/dialogs/kfindtransactiondlg.cpp
+++ b/kmymoney/dialogs/kfindtransactiondlg.cpp
@@ -1,935 +1,937 @@
/***************************************************************************
kfindtransactiondlg.cpp
-------------------
copyright : (C) 2003, 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. *
* *
***************************************************************************/
#include "config-kmymoney.h"
#include "kfindtransactiondlg.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QLabel>
#include <QButtonGroup>
#include <QRadioButton>
#include <QCheckBox>
#include <QTimer>
#include <QTabWidget>
#include <QKeyEvent>
#include <QList>
#include <QEvent>
#include <QPushButton>
#include <QDialogButtonBox>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLineEdit>
#include <KComboBox>
#include <KHelpClient>
#include <KGuiItem>
#include <KStandardGuiItem>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneyedit.h"
#include "mymoneyfile.h"
+#include "mymoneypayee.h"
+#include "mymoneytag.h"
#include "kmymoneyglobalsettings.h"
#include "register.h"
#include "transaction.h"
#include "daterangedlg.h"
#include "ui_kfindtransactiondlgdecl.h"
#include "ui_ksortoptiondlg.h"
enum ItemRoles {
ItemIdRole = Qt::UserRole
};
struct KSortOptionDlg::Private {
Ui::KSortOptionDlg ui;
};
KSortOptionDlg::KSortOptionDlg(QWidget *parent)
: QDialog(parent), d(new Private)
{
d->ui.setupUi(this);
init();
}
KSortOptionDlg::~KSortOptionDlg()
{
delete d;
}
void KSortOptionDlg::init()
{
}
void KSortOptionDlg::setSortOption(const QString& option, const QString& def)
{
if (option.isEmpty()) {
d->ui.m_sortOption->setSettings(def);
d->ui.m_useDefault->setChecked(true);
} else {
d->ui.m_sortOption->setSettings(option);
d->ui.m_useDefault->setChecked(false);
}
}
QString KSortOptionDlg::sortOption() const
{
QString rc;
if (!d->ui.m_useDefault->isChecked()) {
rc = d->ui.m_sortOption->settings();
}
return rc;
}
void KSortOptionDlg::hideDefaultButton()
{
d->ui.m_useDefault->hide();
}
KFindTransactionDlg::KFindTransactionDlg(QWidget *parent, bool withEquityAccounts)
: QDialog(parent)
, m_needReload(false)
, m_ui(new Ui::KFindTransactionDlgDecl)
{
m_ui->setupUi(this);
m_dateRange = new DateRangeDlg;
m_ui->dateRangeLayout->insertWidget(0, m_dateRange);
m_ui->ButtonGroup1->setId(m_ui->m_amountButton, 0);
m_ui->ButtonGroup1->setId(m_ui->m_amountRangeButton, 1);
m_ui->m_register->installEventFilter(this);
m_ui->m_tabWidget->setTabEnabled(m_ui->m_tabWidget->indexOf(m_ui->m_resultPage), false);
// 'cause we don't have a separate setupTextPage
connect(m_ui->m_textEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections()));
// if return is pressed trigger a search (slotSearch checks if it's possible to perform the search)
connect(m_ui->m_textEdit, SIGNAL(returnPressed()), this, SLOT(slotSearch()));
// in case the date selection changes, we update the selection
connect(m_dateRange, SIGNAL(rangeChanged()), this, SLOT(slotUpdateSelections()));
setupAccountsPage(withEquityAccounts);
setupCategoriesPage();
setupAmountPage();
setupPayeesPage();
setupTagsPage();
setupDetailsPage();
// We don't need to add the default into the list (see ::slotShowHelp() why)
// m_helpAnchor[m_ui->m_textTab] = QLatin1String("details.search");
m_helpAnchor[m_ui->m_accountTab] = QLatin1String("details.search.account");
m_helpAnchor[m_ui->m_dateTab] = QLatin1String("details.search.date");
m_helpAnchor[m_ui->m_amountTab] = QLatin1String("details.search.amount");
m_helpAnchor[m_ui->m_categoryTab] = QLatin1String("details.search.category");
m_helpAnchor[m_ui->m_payeeTab] = QLatin1String("details.search.payee");
m_helpAnchor[m_ui->m_tagTab] = QLatin1String("details.search.tag"); //FIXME-ALEX update Help
m_helpAnchor[m_ui->m_detailsTab] = QLatin1String("details.search.details");
// setup the register
QList<KMyMoneyRegister::Column> cols;
cols << KMyMoneyRegister::DateColumn;
cols << KMyMoneyRegister::AccountColumn;
cols << KMyMoneyRegister::DetailColumn;
cols << KMyMoneyRegister::ReconcileFlagColumn;
cols << KMyMoneyRegister::PaymentColumn;
cols << KMyMoneyRegister::DepositColumn;
m_ui->m_register->setupRegister(MyMoneyAccount(), cols);
m_ui->m_register->setSelectionMode(QTableWidget::SingleSelection);
connect(m_ui->m_register, SIGNAL(editTransaction()), this, SLOT(slotSelectTransaction()));
connect(m_ui->m_register->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotSortOptions()));
slotUpdateSelections();
// setup the connections
connect(m_ui->buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotSearch()));
connect(m_ui->buttonBox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), this, SLOT(slotReset()));
connect(m_ui->buttonBox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), m_ui->m_accountsView, SLOT(slotSelectAllAccounts()));
connect(m_ui->buttonBox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), m_ui->m_categoriesView, SLOT(slotSelectAllAccounts()));
connect(m_ui->buttonBox->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(deleteLater()));
connect(m_ui->buttonBox->button(QDialogButtonBox::Help), SIGNAL(clicked()), this, SLOT(slotShowHelp()));
// only allow searches when a selection has been made
m_ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
KGuiItem::assign(m_ui->buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::find());
m_ui->buttonBox->button(QDialogButtonBox::Apply)->setToolTip(i18nc("@info:tooltip for find transaction apply button", "Search transactions"));
connect(this, SIGNAL(selectionNotEmpty(bool)), m_ui->buttonBox->button(QDialogButtonBox::Apply), SLOT(setEnabled(bool)));
// get signal about engine changes
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotRefreshView()));
slotUpdateSelections();
m_ui->m_textEdit->setFocus();
}
KFindTransactionDlg::~KFindTransactionDlg()
{
delete m_ui;
}
void KFindTransactionDlg::slotReset()
{
m_ui->m_textEdit->setText(QString());
m_ui->m_regExp->setChecked(false);
m_ui->m_caseSensitive->setChecked(false);
m_ui->m_textNegate->setCurrentItem(0);
m_ui->m_amountEdit->setEnabled(true);
m_ui->m_amountFromEdit->setEnabled(false);
m_ui->m_amountToEdit->setEnabled(false);
m_ui->m_amountEdit->loadText(QString());
m_ui->m_amountFromEdit->loadText(QString());
m_ui->m_amountToEdit->loadText(QString());
m_ui->m_amountButton->setChecked(true);
m_ui->m_amountRangeButton->setChecked(false);
m_ui->m_emptyPayeesButton->setChecked(false);
selectAllItems(m_ui->m_payeesView, true);
m_ui->m_emptyTagsButton->setChecked(false);
selectAllItems(m_ui->m_tagsView, true);
m_ui->m_typeBox->setCurrentIndex(MyMoneyTransactionFilter::allTypes);
- m_ui->m_stateBox->setCurrentIndex(MyMoneyTransactionFilter::allStates);
+ m_ui->m_stateBox->setCurrentIndex((int)eMyMoney::TransactionFilter::State::All);
m_ui->m_validityBox->setCurrentIndex(MyMoneyTransactionFilter::anyValidity);
m_ui->m_nrEdit->setEnabled(true);
m_ui->m_nrFromEdit->setEnabled(false);
m_ui->m_nrToEdit->setEnabled(false);
m_ui->m_nrEdit->setText(QString());
m_ui->m_nrFromEdit->setText(QString());
m_ui->m_nrToEdit->setText(QString());
m_ui->m_nrButton->setChecked(true);
m_ui->m_nrRangeButton->setChecked(false);
m_ui->m_tabWidget->setTabEnabled(m_ui->m_tabWidget->indexOf(m_ui->m_resultPage), false);
m_ui->m_tabWidget->setCurrentIndex(m_ui->m_tabWidget->indexOf(m_ui->m_criteriaTab));
// the following call implies a call to slotUpdateSelections,
// that's why we call it last
m_dateRange->slotReset();
slotUpdateSelections();
}
void KFindTransactionDlg::slotUpdateSelections()
{
QString txt;
// Text tab
if (!m_ui->m_textEdit->text().isEmpty()) {
if (!txt.isEmpty())
txt += ", ";
txt += i18n("Text");
m_ui->m_regExp->setEnabled(QRegExp(m_ui->m_textEdit->text()).isValid());
} else
m_ui->m_regExp->setEnabled(false);
m_ui->m_caseSensitive->setEnabled(!m_ui->m_textEdit->text().isEmpty());
m_ui->m_textNegate->setEnabled(!m_ui->m_textEdit->text().isEmpty());
// Account tab
if (!m_ui->m_accountsView->allItemsSelected()) {
if (!txt.isEmpty())
txt += ", ";
txt += i18n("Account");
}
if (m_dateRange->dateRange() != MyMoneyTransactionFilter::allDates) {
if (!txt.isEmpty())
txt += ", ";
txt += i18n("Date");
}
// Amount tab
if ((m_ui->m_amountButton->isChecked() && m_ui->m_amountEdit->isValid())
|| (m_ui->m_amountRangeButton->isChecked()
&& (m_ui->m_amountFromEdit->isValid() || m_ui->m_amountToEdit->isValid()))) {
if (!txt.isEmpty())
txt += ", ";
txt += i18n("Amount");
}
// Categories tab
if (!m_ui->m_categoriesView->allItemsSelected()) {
if (!txt.isEmpty())
txt += ", ";
txt += i18n("Category");
}
// Tags tab
if (!allItemsSelected(m_ui->m_tagsView)
|| m_ui->m_emptyTagsButton->isChecked()) {
if (!txt.isEmpty())
txt += ", ";
txt += i18n("Tags");
}
m_ui->m_tagsView->setEnabled(!m_ui->m_emptyTagsButton->isChecked());
// Payees tab
if (!allItemsSelected(m_ui->m_payeesView)
|| m_ui->m_emptyPayeesButton->isChecked()) {
if (!txt.isEmpty())
txt += ", ";
txt += i18n("Payees");
}
m_ui->m_payeesView->setEnabled(!m_ui->m_emptyPayeesButton->isChecked());
// Details tab
if (m_ui->m_typeBox->currentIndex() != 0
|| m_ui->m_stateBox->currentIndex() != 0
|| m_ui->m_validityBox->currentIndex() != 0
|| (m_ui->m_nrButton->isChecked() && m_ui->m_nrEdit->text().length() != 0)
|| (m_ui->m_nrRangeButton->isChecked()
&& (m_ui->m_nrFromEdit->text().length() != 0 || m_ui->m_nrToEdit->text().length() != 0))) {
if (!txt.isEmpty())
txt += ", ";
txt += i18n("Details");
}
//Show a warning about transfers if Categories are filtered - bug #1523508
if (!m_ui->m_categoriesView->allItemsSelected()) {
m_ui->m_transferWarning->setText(i18n("Warning: Filtering by Category will exclude all transfers from the results."));
} else {
m_ui->m_transferWarning->setText("");
}
// disable the search button if no selection is made
emit selectionNotEmpty(!txt.isEmpty());
if (txt.isEmpty()) {
txt = i18nc("No selection", "(None)");
}
m_ui->m_selectedCriteria->setText(i18n("Current selections: %1", txt));
}
bool KFindTransactionDlg::allItemsSelected(const QTreeWidgetItem *item) const
{
QTreeWidgetItem* it_v;
for (int i = 0; i < item->childCount(); ++i) {
it_v = item->child(i);
if (!(it_v->checkState(0) == Qt::Checked && allItemsSelected(it_v))) {
return false;
}
}
return true;
}
bool KFindTransactionDlg::allItemsSelected(const QTreeWidget* view) const
{
QTreeWidgetItem* it_v;
for (int i = 0; i < view->invisibleRootItem()->childCount(); ++i) {
it_v = view->invisibleRootItem()->child(i);
if (it_v->flags() & Qt::ItemIsUserCheckable) {
if (!(it_v->checkState(0) == Qt::Checked && allItemsSelected(it_v))) {
return false;
} else {
if (!allItemsSelected(it_v))
return false;
}
}
}
return true;
}
void KFindTransactionDlg::setupAccountsPage(bool withEquityAccounts)
{
m_ui->m_accountsView->setSelectionMode(QTreeWidget::MultiSelection);
AccountSet accountSet;
- accountSet.addAccountGroup(MyMoneyAccount::Asset);
- accountSet.addAccountGroup(MyMoneyAccount::Liability);
+ accountSet.addAccountGroup(eMyMoney::Account::Asset);
+ accountSet.addAccountGroup(eMyMoney::Account::Liability);
if (withEquityAccounts)
- accountSet.addAccountGroup(MyMoneyAccount::Equity);
+ accountSet.addAccountGroup(eMyMoney::Account::Equity);
//set the accountset to show closed account if the settings say so
accountSet.setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts());
accountSet.load(m_ui->m_accountsView);
connect(m_ui->m_accountsView, SIGNAL(stateChanged()), this, SLOT(slotUpdateSelections()));
}
void KFindTransactionDlg::selectAllItems(QTreeWidget* view, const bool state)
{
QTreeWidgetItem* it_v;
for (int i = 0; i < view->invisibleRootItem()->childCount(); ++i) {
it_v = view->invisibleRootItem()->child(i);
if (it_v->flags() & Qt::ItemIsUserCheckable) {
it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked);
}
selectAllSubItems(it_v, state);
}
slotUpdateSelections();
}
void KFindTransactionDlg::selectItems(QTreeWidget* view, const QStringList& list, const bool state)
{
QTreeWidgetItem* it_v;
for (int i = 0; i < view->invisibleRootItem()->childCount(); ++i) {
it_v = view->invisibleRootItem()->child(i);
QVariant idData = it_v->data(0, ItemIdRole);
if (it_v->flags() & Qt::ItemIsUserCheckable && list.contains(idData.toString())) {
it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked);
}
selectSubItems(it_v, list, state);
}
slotUpdateSelections();
}
void KFindTransactionDlg::setupCategoriesPage()
{
m_ui->m_categoriesView->setSelectionMode(QTreeWidget::MultiSelection);
AccountSet categorySet;
- categorySet.addAccountGroup(MyMoneyAccount::Income);
- categorySet.addAccountGroup(MyMoneyAccount::Expense);
+ categorySet.addAccountGroup(eMyMoney::Account::Income);
+ categorySet.addAccountGroup(eMyMoney::Account::Expense);
categorySet.load(m_ui->m_categoriesView);
connect(m_ui->m_categoriesView, SIGNAL(stateChanged()), this, SLOT(slotUpdateSelections()));
}
void KFindTransactionDlg::selectAllSubItems(QTreeWidgetItem* item, const bool state)
{
QTreeWidgetItem* it_v;
for (int i = 0; i < item->childCount(); ++i) {
it_v = item->child(i);
it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked);
selectAllSubItems(it_v, state);
}
}
void KFindTransactionDlg::selectSubItems(QTreeWidgetItem* item, const QStringList& list, const bool state)
{
QTreeWidgetItem* it_v;
for (int i = 0; i < item->childCount(); ++i) {
it_v = item->child(i);
QVariant idData = it_v->data(0, ItemIdRole);
if (list.contains(idData.toString()))
it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked);
selectSubItems(it_v, list, state);
}
}
void KFindTransactionDlg::setupAmountPage()
{
connect(m_ui->m_amountButton, SIGNAL(clicked()), this, SLOT(slotAmountSelected()));
connect(m_ui->m_amountRangeButton, SIGNAL(clicked()), this, SLOT(slotAmountRangeSelected()));
connect(m_ui->m_amountEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections()));
connect(m_ui->m_amountFromEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections()));
connect(m_ui->m_amountToEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections()));
m_ui->m_amountButton->setChecked(true);
slotAmountSelected();
}
void KFindTransactionDlg::slotAmountSelected()
{
m_ui->m_amountEdit->setEnabled(true);
m_ui->m_amountFromEdit->setEnabled(false);
m_ui->m_amountToEdit->setEnabled(false);
slotUpdateSelections();
}
void KFindTransactionDlg::slotAmountRangeSelected()
{
m_ui->m_amountEdit->setEnabled(false);
m_ui->m_amountFromEdit->setEnabled(true);
m_ui->m_amountToEdit->setEnabled(true);
slotUpdateSelections();
}
void KFindTransactionDlg::setupPayeesPage()
{
m_ui->m_payeesView->setSelectionMode(QAbstractItemView::SingleSelection);
m_ui->m_payeesView->header()->hide();
m_ui->m_payeesView->setAlternatingRowColors(true);
loadPayees();
m_ui->m_payeesView->sortItems(0, Qt::AscendingOrder);
m_ui->m_emptyPayeesButton->setCheckState(Qt::Unchecked);
connect(m_ui->m_allPayeesButton, SIGNAL(clicked()), this, SLOT(slotSelectAllPayees()));
connect(m_ui->m_clearPayeesButton, SIGNAL(clicked()), this, SLOT(slotDeselectAllPayees()));
connect(m_ui->m_emptyPayeesButton, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateSelections()));
connect(m_ui->m_payeesView, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotUpdateSelections()));
}
void KFindTransactionDlg::loadPayees()
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyPayee> list;
QList<MyMoneyPayee>::Iterator it_l;
list = file->payeeList();
// load view
for (it_l = list.begin(); it_l != list.end(); ++it_l) {
QTreeWidgetItem* item = new QTreeWidgetItem(m_ui->m_payeesView);
item->setText(0, (*it_l).name());
item->setData(0, ItemIdRole, (*it_l).id());
item->setCheckState(0, Qt::Checked);
}
}
void KFindTransactionDlg::slotSelectAllPayees()
{
selectAllItems(m_ui->m_payeesView, true);
}
void KFindTransactionDlg::slotDeselectAllPayees()
{
selectAllItems(m_ui->m_payeesView, false);
}
void KFindTransactionDlg::setupTagsPage()
{
m_ui->m_tagsView->setSelectionMode(QAbstractItemView::SingleSelection);
m_ui->m_tagsView->header()->hide();
m_ui->m_tagsView->setAlternatingRowColors(true);
loadTags();
m_ui->m_tagsView->sortItems(0, Qt::AscendingOrder);
m_ui->m_emptyTagsButton->setCheckState(Qt::Unchecked);
connect(m_ui->m_allTagsButton, SIGNAL(clicked()), this, SLOT(slotSelectAllTags()));
connect(m_ui->m_clearTagsButton, SIGNAL(clicked()), this, SLOT(slotDeselectAllTags()));
connect(m_ui->m_emptyTagsButton, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateSelections()));
connect(m_ui->m_tagsView, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotUpdateSelections()));
}
void KFindTransactionDlg::loadTags()
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyTag> list;
QList<MyMoneyTag>::Iterator it_l;
list = file->tagList();
// load view
for (it_l = list.begin(); it_l != list.end(); ++it_l) {
QTreeWidgetItem* item = new QTreeWidgetItem(m_ui->m_tagsView);
item->setText(0, (*it_l).name());
item->setData(0, ItemIdRole, (*it_l).id());
item->setCheckState(0, Qt::Checked);
}
}
void KFindTransactionDlg::slotSelectAllTags()
{
selectAllItems(m_ui->m_tagsView, true);
}
void KFindTransactionDlg::slotDeselectAllTags()
{
selectAllItems(m_ui->m_tagsView, false);
}
void KFindTransactionDlg::setupDetailsPage()
{
connect(m_ui->m_typeBox, SIGNAL(activated(int)), this, SLOT(slotUpdateSelections()));
connect(m_ui->m_stateBox, SIGNAL(activated(int)), this, SLOT(slotUpdateSelections()));
connect(m_ui->m_validityBox, SIGNAL(activated(int)), this, SLOT(slotUpdateSelections()));
connect(m_ui->m_nrButton, SIGNAL(clicked()), this, SLOT(slotNrSelected()));
connect(m_ui->m_nrRangeButton, SIGNAL(clicked()), this, SLOT(slotNrRangeSelected()));
connect(m_ui->m_nrEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections()));
connect(m_ui->m_nrFromEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections()));
connect(m_ui->m_nrToEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections()));
m_ui->m_nrButton->setChecked(true);
slotNrSelected();
}
void KFindTransactionDlg::slotNrSelected()
{
m_ui->m_nrEdit->setEnabled(true);
m_ui->m_nrFromEdit->setEnabled(false);
m_ui->m_nrToEdit->setEnabled(false);
slotUpdateSelections();
}
void KFindTransactionDlg::slotNrRangeSelected()
{
m_ui->m_nrEdit->setEnabled(false);
m_ui->m_nrFromEdit->setEnabled(true);
m_ui->m_nrToEdit->setEnabled(true);
slotUpdateSelections();
}
void KFindTransactionDlg::addItemToFilter(const opTypeE op, const QString& id)
{
switch (op) {
case addAccountToFilter:
m_filter.addAccount(id);
break;
case addCategoryToFilter:
m_filter.addCategory(id);
break;
case addPayeeToFilter:
m_filter.addPayee(id);
break;
case addTagToFilter:
m_filter.addTag(id);
break;
}
}
void KFindTransactionDlg::scanCheckListItems(const QTreeWidgetItem* item, const opTypeE op)
{
QTreeWidgetItem* it_v;
for (int i = 0; i < item->childCount(); ++i) {
it_v = item->child(i);
QVariant idData = it_v->data(0, ItemIdRole);
if (it_v->flags() & Qt::ItemIsUserCheckable) {
if (it_v->checkState(0) == Qt::Checked)
addItemToFilter(op, idData.toString());
}
scanCheckListItems(it_v, op);
}
}
void KFindTransactionDlg::scanCheckListItems(const QTreeWidget* view, const opTypeE op)
{
QTreeWidgetItem* it_v;
for (int i = 0; i < view->invisibleRootItem()->childCount(); ++i) {
it_v = view->invisibleRootItem()->child(i);
QVariant idData = it_v->data(0, ItemIdRole);
if (it_v->flags() & Qt::ItemIsUserCheckable) {
if (it_v->checkState(0) == Qt::Checked) {
addItemToFilter(op, idData.toString());
}
}
scanCheckListItems(it_v, op);
}
}
void KFindTransactionDlg::setupFilter()
{
m_filter.clear();
// Text tab
if (!m_ui->m_textEdit->text().isEmpty()) {
QRegExp exp(m_ui->m_textEdit->text(), m_ui->m_caseSensitive->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive, !m_ui->m_regExp->isChecked() ? QRegExp::Wildcard : QRegExp::RegExp);
m_filter.setTextFilter(exp, m_ui->m_textNegate->currentIndex() != 0);
}
// Account tab
if (!m_ui->m_accountsView->allItemsSelected()) {
// retrieve a list of selected accounts
QStringList list;
m_ui->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.constBegin(); it_a != list.constEnd(); ++it_a) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a);
- if (acc.accountType() == MyMoneyAccount::Investment) {
+ if (acc.accountType() == eMyMoney::Account::Investment) {
for (it_b = acc.accountList().constBegin(); it_b != acc.accountList().constEnd(); ++it_b) {
if (!list.contains(*it_b)) {
missing.append(*it_b);
}
}
}
}
list += missing;
}
m_filter.addAccount(list);
}
// Date tab
if (m_dateRange->dateRange() != 0) {
m_filter.setDateFilter(m_dateRange->fromDate(), m_dateRange->toDate());
}
// Amount tab
if ((m_ui->m_amountButton->isChecked() && m_ui->m_amountEdit->isValid())) {
m_filter.setAmountFilter(m_ui->m_amountEdit->value(), m_ui->m_amountEdit->value());
} else if ((m_ui->m_amountRangeButton->isChecked()
&& (m_ui->m_amountFromEdit->isValid() || m_ui->m_amountToEdit->isValid()))) {
MyMoneyMoney from(MyMoneyMoney::minValue), to(MyMoneyMoney::maxValue);
if (m_ui->m_amountFromEdit->isValid())
from = m_ui->m_amountFromEdit->value();
if (m_ui->m_amountToEdit->isValid())
to = m_ui->m_amountToEdit->value();
m_filter.setAmountFilter(from, to);
}
// Categories tab
if (!m_ui->m_categoriesView->allItemsSelected()) {
m_filter.addCategory(m_ui->m_categoriesView->selectedItems());
}
// Tags tab
if (m_ui->m_emptyTagsButton->isChecked()) {
m_filter.addTag(QString());
} else if (!allItemsSelected(m_ui->m_tagsView)) {
scanCheckListItems(m_ui->m_tagsView, addTagToFilter);
}
// Payees tab
if (m_ui->m_emptyPayeesButton->isChecked()) {
m_filter.addPayee(QString());
} else if (!allItemsSelected(m_ui->m_payeesView)) {
scanCheckListItems(m_ui->m_payeesView, addPayeeToFilter);
}
// Details tab
if (m_ui->m_typeBox->currentIndex() != 0)
m_filter.addType(m_ui->m_typeBox->currentIndex());
if (m_ui->m_stateBox->currentIndex() != 0)
m_filter.addState(m_ui->m_stateBox->currentIndex());
if (m_ui->m_validityBox->currentIndex() != 0)
m_filter.addValidity(m_ui->m_validityBox->currentIndex());
if (m_ui->m_nrButton->isChecked() && !m_ui->m_nrEdit->text().isEmpty())
m_filter.setNumberFilter(m_ui->m_nrEdit->text(), m_ui->m_nrEdit->text());
if (m_ui->m_nrRangeButton->isChecked()
&& (!m_ui->m_nrFromEdit->text().isEmpty() || !m_ui->m_nrToEdit->text().isEmpty())) {
m_filter.setNumberFilter(m_ui->m_nrFromEdit->text(), m_ui->m_nrToEdit->text());
}
}
void KFindTransactionDlg::slotSearch()
{
// perform the search only if the button is enabled
if (!m_ui->buttonBox->button(QDialogButtonBox::Apply)->isEnabled())
return;
// setup the filter from the dialog widgets
setupFilter();
// filter is setup, now fill the register
slotRefreshView();
m_ui->m_register->setFocus();
}
void KFindTransactionDlg::slotRefreshView()
{
m_needReload = true;
if (isVisible()) {
loadView();
m_needReload = false;
}
}
void KFindTransactionDlg::showEvent(QShowEvent* event)
{
if (m_needReload) {
loadView();
m_needReload = false;
}
QDialog::showEvent(event);
}
void KFindTransactionDlg::loadView()
{
// setup sort order
m_ui->m_register->setSortOrder(KMyMoneyGlobalSettings::sortSearchView());
// clear out old data
m_ui->m_register->clear();
// retrieve the list from the engine
MyMoneyFile::instance()->transactionList(m_transactionList, m_filter);
// create the elements for the register
QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
QMap<QString, int>uniqueMap;
MyMoneyMoney deposit, payment;
int splitCount = 0;
for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) {
const MyMoneySplit& split = (*it).second;
MyMoneyAccount acc = MyMoneyFile::instance()->account(split.accountId());
++splitCount;
uniqueMap[(*it).first.id()]++;
KMyMoneyRegister::Register::transactionFactory(m_ui->m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]);
{ // debug stuff
if (split.shares().isNegative()) {
payment += split.shares().abs();
} else {
deposit += split.shares().abs();
}
}
}
// add the group markers
m_ui->m_register->addGroupMarkers();
// sort the transactions according to the sort setting
m_ui->m_register->sortItems();
// remove trailing and adjacent markers
m_ui->m_register->removeUnwantedGroupMarkers();
// turn on the ledger lens for the register
m_ui->m_register->setLedgerLensForced();
m_ui->m_register->updateRegister(true);
m_ui->m_register->setFocusToTop();
m_ui->m_register->selectItem(m_ui->m_register->focusItem());
#ifdef KMM_DEBUG
m_ui->m_foundText->setText(i18np("Found %1 matching transaction (D %2 / P %3 = %4)",
"Found %1 matching transactions (D %2 / P %3 = %4)", splitCount, deposit.formatMoney("", 2), payment.formatMoney("", 2), (deposit - payment).formatMoney("", 2)));
#else
m_ui->m_foundText->setText(i18np("Found %1 matching transaction", "Found %1 matching transactions", splitCount));
#endif
m_ui->m_tabWidget->setTabEnabled(m_ui->m_tabWidget->indexOf(m_ui->m_resultPage), true);
m_ui->m_tabWidget->setCurrentIndex(m_ui->m_tabWidget->indexOf(m_ui->m_resultPage));
QTimer::singleShot(10, this, SLOT(slotRightSize()));
}
void KFindTransactionDlg::slotRightSize()
{
m_ui->m_register->update();
}
void KFindTransactionDlg::resizeEvent(QResizeEvent* ev)
{
// Columns
// 1 = Date
// 2 = Account
// 4 = Detail
// 5 = C
// 6 = Payment
// 7 = Deposit
// don't forget the resizer
QDialog::resizeEvent(ev);
if (!m_ui->m_register->isVisible())
return;
// resize the register
int w = m_ui->m_register->contentsRect().width();
int m_debitWidth = 80;
int m_creditWidth = 80;
m_ui->m_register->adjustColumn(1);
m_ui->m_register->adjustColumn(2);
m_ui->m_register->adjustColumn(5);
m_ui->m_register->setColumnWidth(6, m_debitWidth);
m_ui->m_register->setColumnWidth(7, m_creditWidth);
for (int i = 0; i < m_ui->m_register->columnCount(); ++i) {
switch (i) {
case 4: // skip the one, we want to set
break;
default:
w -= m_ui->m_register->columnWidth(i);
break;
}
}
m_ui->m_register->setColumnWidth(4, w);
}
void KFindTransactionDlg::slotSelectTransaction()
{
QList<KMyMoneyRegister::RegisterItem*> list = m_ui->m_register->selectedItems();
if (!list.isEmpty()) {
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(list[0]);
if (t) {
emit transactionSelected(t->split().accountId(), t->transaction().id());
hide();
}
}
}
bool KFindTransactionDlg::eventFilter(QObject* o, QEvent* e)
{
bool rc = false;
if (o->isWidgetType()) {
if (e->type() == QEvent::KeyPress) {
const QWidget* w = dynamic_cast<const QWidget*>(o);
QKeyEvent *k = static_cast<QKeyEvent *>(e);
if (w == m_ui->m_register) {
switch (k->key()) {
default:
break;
case Qt::Key_Return:
case Qt::Key_Enter:
rc = true;
slotSelectTransaction();
break;
}
}
}
}
return rc;
}
void KFindTransactionDlg::slotShowHelp()
{
QString anchor = m_helpAnchor[m_ui->m_criteriaTab->currentWidget()];
if (anchor.isEmpty())
anchor = QString("details.search");
KHelpClient::invokeHelp(anchor);
}
void KFindTransactionDlg::slotSortOptions()
{
QPointer<KSortOptionDlg> dlg = new KSortOptionDlg(this);
dlg->setSortOption(KMyMoneyGlobalSettings::sortSearchView(), QString());
dlg->hideDefaultButton();
if (dlg->exec() == QDialog::Accepted) {
QString sortOrder = dlg->sortOption();
if (sortOrder != KMyMoneyGlobalSettings::sortSearchView()) {
KMyMoneyGlobalSettings::setSortSearchView(sortOrder);
slotRefreshView();
}
}
delete dlg;
}
diff --git a/kmymoney/dialogs/kmymoneyfileinfodlg.cpp b/kmymoney/dialogs/kmymoneyfileinfodlg.cpp
index 8a72f7297..03086bc85 100644
--- a/kmymoney/dialogs/kmymoneyfileinfodlg.cpp
+++ b/kmymoney/dialogs/kmymoneyfileinfodlg.cpp
@@ -1,88 +1,88 @@
/***************************************************************************
kmymoneyfileinfodlg.cpp - description
-------------------
begin : Sun Oct 9 2005
copyright : (C) 2005 by Thomas Baumgart
email : Thomas Baumgart <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 "kmymoneyfileinfodlg.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QLabel>
#include <QList>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include <imymoneystorage.h>
#include "mymoneyfile.h"
#include "kmymoneyutils.h"
KMyMoneyFileInfoDlg::KMyMoneyFileInfoDlg(QWidget *parent)
: KMyMoneyFileInfoDlgDecl(parent)
{
// Now fill the fields with data
IMyMoneyStorage* storage = MyMoneyFile::instance()->storage();
m_creationDate->setText(storage->creationDate().toString(Qt::ISODate));
m_lastModificationDate->setText(storage->lastModificationDate().toString(Qt::ISODate));
m_baseCurrency->setText(storage->value("kmm-baseCurrency"));
m_payeeCount->setText(QString("%1").arg(storage->payeeList().count()));
m_institutionCount->setText(QString("%1").arg(storage->institutionList().count()));
QList<MyMoneyAccount> a_list;
storage->accountList(a_list);
m_accountCount->setText(QString("%1").arg(a_list.count()));
- QMap<MyMoneyAccount::accountTypeE, int> accountMap;
- QMap<MyMoneyAccount::accountTypeE, int> accountMapClosed;
+ QMap<eMyMoney::Account, int> accountMap;
+ QMap<eMyMoney::Account, int> accountMapClosed;
QList<MyMoneyAccount>::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<MyMoneyAccount::accountTypeE, int>::const_iterator it_m;
+ QMap<eMyMoney::Account, int>::const_iterator it_m;
for (it_m = accountMap.constBegin(); it_m != accountMap.constEnd(); ++it_m) {
QTreeWidgetItem *item = new QTreeWidgetItem();
item->setText(0, KMyMoneyUtils::accountTypeToString(it_m.key()));
item->setText(1, QString("%1").arg(*it_m));
item->setText(2, QString("%1").arg(accountMapClosed[it_m.key()]));
m_accountView->invisibleRootItem()->addChild(item);
}
MyMoneyTransactionFilter filter;
filter.setReportAllSplits(false);
m_transactionCount->setText(QString("%1").arg(storage->transactionList(filter).count()));
filter.setReportAllSplits(true);
m_splitCount->setText(QString("%1").arg(storage->transactionList(filter).count()));
m_scheduleCount->setText(QString("%1").arg(storage->scheduleList().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();
m_priceCount->setText(QString("%1").arg(pCount));
}
KMyMoneyFileInfoDlg::~KMyMoneyFileInfoDlg()
{
}
diff --git a/kmymoney/dialogs/kmymoneysplittable.cpp b/kmymoney/dialogs/kmymoneysplittable.cpp
index cc46b8f58..0db971b50 100644
--- a/kmymoney/dialogs/kmymoneysplittable.cpp
+++ b/kmymoney/dialogs/kmymoneysplittable.cpp
@@ -1,970 +1,971 @@
/***************************************************************************
kmymoneysplittable.cpp - description
-------------------
begin : Thu Jan 10 2002
copyright : (C) 2000-2002 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "kmymoneysplittable.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QCursor>
#include <QApplication>
#include <QTimer>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QFrame>
#include <QMouseEvent>
#include <QEvent>
#include <QPushButton>
#include <QMenu>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KMessageBox>
#include <KCompletionBox>
#include <KSharedConfig>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneyprice.h"
#include "kmymoneyedit.h"
#include "kmymoneycategory.h"
#include "kmymoneyaccountselector.h"
#include "kmymoneylineedit.h"
#include "mymoneysecurity.h"
#include "kmymoneyglobalsettings.h"
#include "kcurrencycalculator.h"
#include "mymoneyutils.h"
#include "icons.h"
using namespace Icons;
kMyMoneySplitTable::kMyMoneySplitTable(QWidget *parent) :
QTableWidget(parent),
m_currentRow(0),
m_maxRows(0),
m_precision(2),
m_editCategory(0),
m_editMemo(0),
m_editAmount(0)
{
// used for custom coloring with the help of the application's stylesheet
setObjectName(QLatin1String("splittable"));
// setup the transactions table
setRowCount(1);
setColumnCount(3);
QStringList labels;
labels << i18n("Category") << i18n("Memo") << i18n("Amount");
setHorizontalHeaderLabels(labels);
setSelectionMode(QAbstractItemView::SingleSelection);
setSelectionBehavior(QAbstractItemView::SelectRows);
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
setContentsMargins(0, top, right, bottom);
setFont(KMyMoneyGlobalSettings::listCellFont());
setAlternatingRowColors(true);
verticalHeader()->hide();
horizontalHeader()->setSectionsMovable(false);
horizontalHeader()->setFont(KMyMoneyGlobalSettings::listHeaderFont());
KConfigGroup grp = KSharedConfig::openConfig()->group("SplitTable");
QByteArray columns;
columns = grp.readEntry("HeaderState", columns);
horizontalHeader()->restoreState(columns);
horizontalHeader()->setStretchLastSection(true);
setShowGrid(KMyMoneyGlobalSettings::showGrid());
setEditTriggers(QAbstractItemView::NoEditTriggers);
// setup the context menu
m_contextMenu = new QMenu(this);
m_contextMenu->setTitle(i18n("Split Options"));
m_contextMenu->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewFinancialTransfer]));
m_contextMenu->addAction(QIcon::fromTheme(g_Icons[Icon::DocumentEdit]), i18n("Edit..."), this, SLOT(slotStartEdit()));
m_contextMenuDuplicate = m_contextMenu->addAction(QIcon::fromTheme(g_Icons[Icon::EditCopy]), i18nc("To duplicate a split", "Duplicate"), this, SLOT(slotDuplicateSplit()));
m_contextMenuDelete = m_contextMenu->addAction(QIcon::fromTheme(g_Icons[Icon::EditDelete]),
i18n("Delete..."),
this, SLOT(slotDeleteSplit()));
connect(this, SIGNAL(clicked(QModelIndex)),
this, SLOT(slotSetFocus(QModelIndex)));
connect(this, SIGNAL(transactionChanged(MyMoneyTransaction)),
this, SLOT(slotUpdateData(MyMoneyTransaction)));
installEventFilter(this);
}
kMyMoneySplitTable::~kMyMoneySplitTable()
{
KConfigGroup grp = KSharedConfig::openConfig()->group("SplitTable");
QByteArray columns = horizontalHeader()->saveState();
grp.writeEntry("HeaderState", columns);
grp.sync();
}
int kMyMoneySplitTable::currentRow() const
{
return m_currentRow;
}
void kMyMoneySplitTable::setup(const QMap<QString, MyMoneyMoney>& priceInfo, int precision)
{
m_priceInfo = priceInfo;
m_precision = precision;
}
bool kMyMoneySplitTable::eventFilter(QObject *o, QEvent *e)
{
// MYMONEYTRACER(tracer);
QKeyEvent *k = static_cast<QKeyEvent *>(e);
bool rc = false;
int row = currentRow();
int lines = viewport()->height() / rowHeight(0);
if (e->type() == QEvent::KeyPress && !isEditMode()) {
rc = true;
switch (k->key()) {
case Qt::Key_Up:
if (row)
slotSetFocus(model()->index(row - 1, 0));
break;
case Qt::Key_Down:
if (row < m_transaction.splits().count() - 1)
slotSetFocus(model()->index(row + 1, 0));
break;
case Qt::Key_Home:
slotSetFocus(model()->index(0, 0));
break;
case Qt::Key_End:
slotSetFocus(model()->index(m_transaction.splits().count() - 1, 0));
break;
case Qt::Key_PageUp:
if (lines) {
while (lines-- > 0 && row)
--row;
slotSetFocus(model()->index(row, 0));
}
break;
case Qt::Key_PageDown:
if (row < m_transaction.splits().count() - 1) {
while (lines-- > 0 && row < m_transaction.splits().count() - 1)
++row;
slotSetFocus(model()->index(row, 0));
}
break;
case Qt::Key_Delete:
slotDeleteSplit();
break;
case Qt::Key_Return:
case Qt::Key_Enter:
if (row < m_transaction.splits().count() - 1
&& KMyMoneyGlobalSettings::enterMovesBetweenFields()) {
slotStartEdit();
} else
emit returnPressed();
break;
case Qt::Key_Escape:
emit escapePressed();
break;
case Qt::Key_F2:
slotStartEdit();
break;
default:
rc = true;
// duplicate split
if (Qt::Key_C == k->key() && Qt::ControlModifier == k->modifiers()) {
slotDuplicateSplit();
// new split
} else if (Qt::Key_Insert == k->key() && Qt::ControlModifier == k->modifiers()) {
slotSetFocus(model()->index(m_transaction.splits().count() - 1, 0));
slotStartEdit();
} else if (k->text()[ 0 ].isPrint()) {
KMyMoneyCategory* cat = createEditWidgets(false);
if (cat) {
kMyMoneyLineEdit *le = qobject_cast<kMyMoneyLineEdit*>(cat->lineEdit());
if (le) {
// make sure, the widget receives the key again
// and does not select the text this time
le->setText(k->text());
le->end(false);
le->deselect();
le->skipSelectAll(true);
le->setFocus();
}
}
}
break;
}
} else if (e->type() == QEvent::KeyPress && isEditMode()) {
bool terminate = true;
rc = true;
switch (k->key()) {
// suppress the F2 functionality to start editing in inline edit mode
case Qt::Key_F2:
// suppress the cursor movement in inline edit mode
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_PageUp:
case Qt::Key_PageDown:
break;
case Qt::Key_Return:
case Qt::Key_Enter:
// we cannot call the slot directly, as it destroys the caller of
// this method :-( So we let the event handler take care of calling
// the respective slot using a timeout. For a KLineEdit derived object
// it could be, that at this point the user selected a value from
// a completion list. In this case, we close the completion list and
// do not end editing of the transaction.
if (o->inherits("KLineEdit")) {
KLineEdit* le = dynamic_cast<KLineEdit*>(o);
KCompletionBox* box = le->completionBox(false);
if (box && box->isVisible()) {
terminate = false;
le->completionBox(false)->hide();
}
}
// in case we have the 'enter moves focus between fields', we need to simulate
// a TAB key when the object 'o' points to the category or memo field.
if (KMyMoneyGlobalSettings::enterMovesBetweenFields()) {
if (o == m_editCategory->lineEdit() || o == m_editMemo) {
terminate = false;
QKeyEvent evt(e->type(),
Qt::Key_Tab, k->modifiers(), QString(),
k->isAutoRepeat(), k->count());
QApplication::sendEvent(o, &evt);
}
}
if (terminate) {
QTimer::singleShot(0, this, SLOT(slotEndEditKeyboard()));
}
break;
case Qt::Key_Escape:
// we cannot call the slot directly, as it destroys the caller of
// this method :-( So we let the event handler take care of calling
// the respective slot using a timeout.
QTimer::singleShot(0, this, SLOT(slotCancelEdit()));
break;
default:
rc = false;
break;
}
} else if (e->type() == QEvent::KeyRelease && !isEditMode()) {
// for some reason, we only see a KeyRelease event of the Menu key
// here. In other locations (e.g. Register::eventFilter()) we see
// a KeyPress event. Strange. (ipwizard - 2008-05-10)
switch (k->key()) {
case Qt::Key_Menu:
// if the very last entry is selected, the delete
// operation is not available otherwise it is
m_contextMenuDelete->setEnabled(
row < m_transaction.splits().count() - 1);
m_contextMenuDuplicate->setEnabled(
row < m_transaction.splits().count() - 1);
m_contextMenu->exec(QCursor::pos());
rc = true;
break;
default:
break;
}
}
// if the event has not been processed here, forward it to
// the base class implementation if it's not a key event
if (rc == false) {
if (e->type() != QEvent::KeyPress
&& e->type() != QEvent::KeyRelease) {
rc = QTableWidget::eventFilter(o, e);
}
}
return rc;
}
void kMyMoneySplitTable::slotSetFocus(const QModelIndex& index, int button)
{
MYMONEYTRACER(tracer);
int row = index.row();
// adjust row to used area
if (row > m_transaction.splits().count() - 1)
row = m_transaction.splits().count() - 1;
if (row < 0)
row = 0;
// make sure the row will be on the screen
scrollTo(model()->index(row, 0));
if (isEditMode()) { // in edit mode?
if (isEditSplitValid() && KMyMoneyGlobalSettings::focusChangeIsEnter())
endEdit(false/*keyboard driven*/, false/*set focus to next row*/);
else
slotCancelEdit();
}
if (button == Qt::LeftButton) { // left mouse button
if (row != currentRow()) {
// setup new current row and update visible selection
selectRow(row);
slotUpdateData(m_transaction);
}
} else if (button == Qt::RightButton) {
// context menu is only available when cursor is on
// an existing transaction or the first line after this area
if (row == index.row()) {
// setup new current row and update visible selection
selectRow(row);
slotUpdateData(m_transaction);
// if the very last entry is selected, the delete
// operation is not available otherwise it is
m_contextMenuDelete->setEnabled(
row < m_transaction.splits().count() - 1);
m_contextMenuDuplicate->setEnabled(
row < m_transaction.splits().count() - 1);
m_contextMenu->exec(QCursor::pos());
}
}
}
void kMyMoneySplitTable::mousePressEvent(QMouseEvent* e)
{
slotSetFocus(indexAt(e->pos()), e->button());
}
/* turn off QTable behaviour */
void kMyMoneySplitTable::mouseReleaseEvent(QMouseEvent* /* e */)
{
}
void kMyMoneySplitTable::mouseDoubleClickEvent(QMouseEvent *e)
{
MYMONEYTRACER(tracer);
int col = columnAt(e->pos().x());
slotSetFocus(model()->index(rowAt(e->pos().y()), col), e->button());
createEditWidgets(false);
QLineEdit* editWidget = 0; //krazy:exclude=qmethods
switch (col) {
case 0:
editWidget = m_editCategory->lineEdit();
break;
case 1:
editWidget = m_editMemo;
break;
case 2:
editWidget = m_editAmount->lineedit();
break;
default:
break;
}
if (editWidget) {
editWidget->setFocus();
editWidget->selectAll();
}
}
void kMyMoneySplitTable::selectRow(int row)
{
MYMONEYTRACER(tracer);
if (row > m_maxRows)
row = m_maxRows;
m_currentRow = row;
QTableWidget::selectRow(row);
QList<MyMoneySplit> list = getSplits(m_transaction);
if (row < list.count())
m_split = list[row];
else
m_split = MyMoneySplit();
}
void kMyMoneySplitTable::setRowCount(int irows)
{
QTableWidget::setRowCount(irows);
// determine row height according to the edit widgets
// we use the category widget as the base
QFontMetrics fm(KMyMoneyGlobalSettings::listCellFont());
int height = fm.lineSpacing() + 6;
#if 0
// recalculate row height hint
KMyMoneyCategory cat;
height = qMax(cat.sizeHint().height(), height);
#endif
verticalHeader()->setUpdatesEnabled(false);
for (int i = 0; i < irows; ++i)
verticalHeader()->resizeSection(i, height);
verticalHeader()->setUpdatesEnabled(true);
}
void kMyMoneySplitTable::setTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s, const MyMoneyAccount& acc)
{
MYMONEYTRACER(tracer);
m_transaction = t;
m_account = acc;
m_hiddenSplit = s;
selectRow(0);
slotUpdateData(m_transaction);
}
const QList<MyMoneySplit> kMyMoneySplitTable::getSplits(const MyMoneyTransaction& t) const
{
// get list of splits
QList<MyMoneySplit> list = t.splits();
// and ignore the one that should be hidden
QList<MyMoneySplit>::Iterator it;
for (it = list.begin(); it != list.end(); ++it) {
if ((*it).id() == m_hiddenSplit.id()) {
list.erase(it);
break;
}
}
return list;
}
void kMyMoneySplitTable::slotUpdateData(const MyMoneyTransaction& t)
{
MYMONEYTRACER(tracer);
unsigned long numRows = 0;
QTableWidgetItem* textItem;
QList<MyMoneySplit> list = getSplits(t);
updateTransactionTableSize();
// fill the part that is used by transactions
QList<MyMoneySplit>::Iterator it;
for (it = list.begin(); it != list.end(); ++it) {
QString colText;
MyMoneyMoney value = (*it).value();
if (!(*it).accountId().isEmpty()) {
try {
colText = MyMoneyFile::instance()->accountToCategory((*it).accountId());
} catch (const MyMoneyException &) {
qDebug("Unexpected exception in kMyMoneySplitTable::slotUpdateData()");
}
}
QString amountTxt = value.formatMoney(m_account.fraction());
if (value == MyMoneyMoney::autoCalc) {
amountTxt = i18n("will be calculated");
}
if (colText.isEmpty() && (*it).memo().isEmpty() && value.isZero())
amountTxt.clear();
unsigned width = fontMetrics().width(amountTxt);
kMyMoneyEdit* valfield = new kMyMoneyEdit();
valfield->setMinimumWidth(width);
width = valfield->minimumSizeHint().width();
delete valfield;
textItem = item(numRows, 0);
if (textItem)
textItem->setText(colText);
else
setItem(numRows, 0, new QTableWidgetItem(colText));
textItem = item(numRows, 1);
if (textItem)
textItem->setText((*it).memo());
else
setItem(numRows, 1, new QTableWidgetItem((*it).memo()));
textItem = item(numRows, 2);
if (textItem)
textItem->setText(amountTxt);
else
setItem(numRows, 2, new QTableWidgetItem(amountTxt));
item(numRows, 2)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
++numRows;
}
// now clean out the remainder of the table
while (numRows < static_cast<unsigned long>(rowCount())) {
for (int i = 0 ; i < 3; ++i) {
textItem = item(numRows, i);
if (textItem)
textItem->setText("");
else
setItem(numRows, i, new QTableWidgetItem(""));
}
item(numRows, 2)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
++numRows;
}
}
void kMyMoneySplitTable::updateTransactionTableSize()
{
// get current size of transactions table
int tableHeight = height();
int splitCount = m_transaction.splits().count() - 1;
if (splitCount < 0)
splitCount = 0;
// see if we need some extra lines to fill the current size with the grid
int numExtraLines = (tableHeight / rowHeight(0)) - splitCount;
if (numExtraLines < 2)
numExtraLines = 2;
setRowCount(splitCount + numExtraLines);
m_maxRows = splitCount;
}
void kMyMoneySplitTable::resizeEvent(QResizeEvent* ev)
{
QTableWidget::resizeEvent(ev);
if (!isEditMode()) {
// update the size of the transaction table only if a split is not being edited
// otherwise the height of the editors would be altered in an undesired way
updateTransactionTableSize();
}
}
void kMyMoneySplitTable::slotDuplicateSplit()
{
MYMONEYTRACER(tracer);
QList<MyMoneySplit> list = getSplits(m_transaction);
if (m_currentRow < list.count()) {
MyMoneySplit split = list[m_currentRow];
split.clearId();
try {
m_transaction.addSplit(split);
emit transactionChanged(m_transaction);
} catch (const MyMoneyException &e) {
qDebug("Cannot duplicate split: %s", qPrintable(e.what()));
}
}
}
void kMyMoneySplitTable::slotDeleteSplit()
{
MYMONEYTRACER(tracer);
QList<MyMoneySplit> list = getSplits(m_transaction);
if (m_currentRow < list.count()) {
if (KMessageBox::warningContinueCancel(this,
i18n("You are about to delete the selected split. "
"Do you really want to continue?"),
i18n("KMyMoney"),
KGuiItem(i18n("Continue"))
) == KMessageBox::Continue) {
try {
m_transaction.removeSplit(list[m_currentRow]);
// if we removed the last split, select the previous
if (m_currentRow && m_currentRow == list.count() - 1)
selectRow(m_currentRow - 1);
else
selectRow(m_currentRow);
emit transactionChanged(m_transaction);
} catch (const MyMoneyException &e) {
qDebug("Cannot remove split: %s", qPrintable(e.what()));
}
}
}
}
KMyMoneyCategory* kMyMoneySplitTable::slotStartEdit()
{
MYMONEYTRACER(tracer);
return createEditWidgets(true);
}
void kMyMoneySplitTable::slotEndEdit()
{
endEdit(false);
}
void kMyMoneySplitTable::slotEndEditKeyboard()
{
endEdit(true);
}
void kMyMoneySplitTable::endEdit(bool keyboardDriven, bool setFocusToNextRow)
{
MyMoneyFile* file = MyMoneyFile::instance();
MYMONEYTRACER(tracer);
MyMoneySplit s1 = m_split;
if (!isEditSplitValid()) {
KMessageBox::information(this, i18n("You need to assign a category to this split before it can be entered."), i18n("Enter split"), "EnterSplitWithEmptyCategory");
m_editCategory->setFocus();
return;
}
bool needUpdate = false;
if (m_editCategory->selectedItem() != m_split.accountId()) {
s1.setAccountId(m_editCategory->selectedItem());
needUpdate = true;
}
if (m_editMemo->text() != m_split.memo()) {
s1.setMemo(m_editMemo->text());
needUpdate = true;
}
if (m_editAmount->value() != m_split.value()) {
s1.setValue(m_editAmount->value());
needUpdate = true;
}
if (needUpdate) {
if (!s1.value().isZero()) {
MyMoneyAccount cat = file->account(s1.accountId());
if (cat.currencyId() != m_transaction.commodity()) {
MyMoneySecurity fromCurrency, toCurrency;
MyMoneyMoney fromValue, toValue;
fromCurrency = file->security(m_transaction.commodity());
toCurrency = file->security(cat.currencyId());
// determine the fraction required for this category
int fract = toCurrency.smallestAccountFraction();
- if (cat.accountType() == MyMoneyAccount::Cash)
+ if (cat.accountType() == eMyMoney::Account::Cash)
fract = toCurrency.smallestCashFraction();
// display only positive values to the user
fromValue = s1.value().abs();
// if we had a price info in the beginning, we use it here
if (m_priceInfo.find(cat.currencyId()) != m_priceInfo.end()) {
toValue = (fromValue * m_priceInfo[cat.currencyId()]).convert(fract);
}
// if the shares are still 0, we need to change that
if (toValue.isZero()) {
const MyMoneyPrice &price = MyMoneyFile::instance()->price(fromCurrency.id(), toCurrency.id());
// if the price is valid calculate the shares. If it is invalid
// assume a conversion rate of 1.0
if (price.isValid()) {
toValue = (price.rate(toCurrency.id()) * fromValue).convert(fract);
} else {
toValue = fromValue;
}
}
// now present all that to the user
QPointer<KCurrencyCalculator> calc =
new KCurrencyCalculator(fromCurrency,
toCurrency,
fromValue,
toValue,
m_transaction.postDate(),
fract,
this);
if (calc->exec() == QDialog::Rejected) {
delete calc;
return;
} else {
s1.setShares((s1.value() * calc->price()).convert(fract));
delete calc;
}
} else {
s1.setShares(s1.value());
}
} else
s1.setShares(s1.value());
m_split = s1;
try {
if (m_split.id().isEmpty()) {
m_transaction.addSplit(m_split);
} else {
m_transaction.modifySplit(m_split);
}
emit transactionChanged(m_transaction);
} catch (const MyMoneyException &e) {
qDebug("Cannot add/modify split: %s", qPrintable(e.what()));
}
}
this->setFocus();
destroyEditWidgets();
if (setFocusToNextRow) {
slotSetFocus(model()->index(currentRow() + 1, 0));
}
// if we still have more splits, we start editing right away
// in case we have selected 'enter moves between fields'
if (keyboardDriven
&& currentRow() < m_transaction.splits().count() - 1
&& KMyMoneyGlobalSettings::enterMovesBetweenFields()) {
slotStartEdit();
}
}
void kMyMoneySplitTable::slotCancelEdit()
{
MYMONEYTRACER(tracer);
if (isEditMode()) {
destroyEditWidgets();
this->setFocus();
}
}
bool kMyMoneySplitTable::isEditMode() const
{
// while the edit widgets exist we're in edit mode
return m_editAmount || m_editMemo || m_editCategory;
}
bool kMyMoneySplitTable::isEditSplitValid() const
{
return isEditMode() && !m_editCategory->selectedItem().isEmpty();
}
void kMyMoneySplitTable::destroyEditWidgets()
{
MYMONEYTRACER(tracer);
emit editFinished();
disconnect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadEditWidgets()));
destroyEditWidget(m_currentRow, 0);
destroyEditWidget(m_currentRow, 1);
destroyEditWidget(m_currentRow, 2);
destroyEditWidget(m_currentRow + 1, 0);
}
void kMyMoneySplitTable::destroyEditWidget(int r, int c)
{
if (QWidget* cw = cellWidget(r, c))
cw->hide();
removeCellWidget(r, c);
}
KMyMoneyCategory* kMyMoneySplitTable::createEditWidgets(bool setFocus)
{
MYMONEYTRACER(tracer);
emit editStarted();
QFont cellFont = KMyMoneyGlobalSettings::listCellFont();
m_tabOrderWidgets.clear();
// create the widgets
m_editAmount = new kMyMoneyEdit(0);
m_editAmount->setFont(cellFont);
m_editAmount->setResetButtonVisible(false);
m_editAmount->setPrecision(m_precision);
m_editCategory = new KMyMoneyCategory();
m_editCategory->setPlaceholderText(i18n("Category"));
m_editCategory->setFont(cellFont);
connect(m_editCategory, SIGNAL(createItem(QString,QString&)), this, SIGNAL(createCategory(QString,QString&)));
connect(m_editCategory, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool)));
m_editMemo = new kMyMoneyLineEdit(0, false, Qt::AlignLeft | Qt::AlignVCenter);
m_editMemo->setPlaceholderText(i18n("Memo"));
m_editMemo->setFont(cellFont);
// create buttons for the mouse users
m_registerButtonFrame = new QFrame(this);
m_registerButtonFrame->setContentsMargins(0, 0, 0, 0);
m_registerButtonFrame->setAutoFillBackground(true);
QHBoxLayout* l = new QHBoxLayout(m_registerButtonFrame);
l->setContentsMargins(0, 0, 0, 0);
l->setSpacing(0);
m_registerEnterButton = new QPushButton(QIcon::fromTheme(g_Icons[Icon::DialogOK])
, QString(), m_registerButtonFrame);
m_registerCancelButton = new QPushButton(QIcon::fromTheme(g_Icons[Icon::DialogCancel])
, QString(), m_registerButtonFrame);
l->addWidget(m_registerEnterButton);
l->addWidget(m_registerCancelButton);
l->addStretch(2);
connect(m_registerEnterButton, SIGNAL(clicked()), this, SLOT(slotEndEdit()));
connect(m_registerCancelButton, SIGNAL(clicked()), this, SLOT(slotCancelEdit()));
// setup tab order
addToTabOrder(m_editCategory);
addToTabOrder(m_editMemo);
addToTabOrder(m_editAmount);
addToTabOrder(m_registerEnterButton);
addToTabOrder(m_registerCancelButton);
if (!m_split.accountId().isEmpty()) {
m_editCategory->setSelectedItem(m_split.accountId());
} else {
// check if the transaction is balanced or not. If not,
// assign the remainder to the amount.
MyMoneyMoney diff;
QList<MyMoneySplit> list = m_transaction.splits();
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) {
if (!(*it_s).accountId().isEmpty())
diff += (*it_s).value();
}
m_split.setValue(-diff);
}
m_editMemo->loadText(m_split.memo());
// don't allow automatically calculated values to be modified
if (m_split.value() == MyMoneyMoney::autoCalc) {
m_editAmount->setEnabled(false);
m_editAmount->loadText("will be calculated");
} else
m_editAmount->setValue(m_split.value());
setCellWidget(m_currentRow, 0, m_editCategory);
setCellWidget(m_currentRow, 1, m_editMemo);
setCellWidget(m_currentRow, 2, m_editAmount);
setCellWidget(m_currentRow + 1, 0, m_registerButtonFrame);
// load e.g. the category widget with the account list
slotLoadEditWidgets();
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadEditWidgets()));
foreach (QWidget* w, m_tabOrderWidgets) {
if (w) {
w->installEventFilter(this);
}
}
if (setFocus) {
m_editCategory->lineEdit()->setFocus();
m_editCategory->lineEdit()->selectAll();
}
// resize the rows so the added edit widgets would fit appropriately
resizeRowsToContents();
return m_editCategory;
}
void kMyMoneySplitTable::slotLoadEditWidgets()
{
// reload category widget
QString categoryId = m_editCategory->selectedItem();
AccountSet aSet;
- aSet.addAccountGroup(MyMoneyAccount::Asset);
- aSet.addAccountGroup(MyMoneyAccount::Liability);
- aSet.addAccountGroup(MyMoneyAccount::Income);
- aSet.addAccountGroup(MyMoneyAccount::Expense);
+ aSet.addAccountGroup(eMyMoney::Account::Asset);
+ aSet.addAccountGroup(eMyMoney::Account::Liability);
+ aSet.addAccountGroup(eMyMoney::Account::Income);
+ aSet.addAccountGroup(eMyMoney::Account::Expense);
if (KMyMoneyGlobalSettings::expertMode())
- aSet.addAccountGroup(MyMoneyAccount::Equity);
+ aSet.addAccountGroup(eMyMoney::Account::Equity);
// remove the accounts with invalid types at this point
- aSet.removeAccountType(MyMoneyAccount::CertificateDep);
- aSet.removeAccountType(MyMoneyAccount::Investment);
- aSet.removeAccountType(MyMoneyAccount::Stock);
- aSet.removeAccountType(MyMoneyAccount::MoneyMarket);
+ aSet.removeAccountType(eMyMoney::Account::CertificateDep);
+ aSet.removeAccountType(eMyMoney::Account::Investment);
+ aSet.removeAccountType(eMyMoney::Account::Stock);
+ aSet.removeAccountType(eMyMoney::Account::MoneyMarket);
aSet.load(m_editCategory->selector());
// if an account is specified then remove it from the widget so that the user
// cannot create a transfer with from and to account being the same account
if (!m_account.id().isEmpty())
m_editCategory->selector()->removeItem(m_account.id());
if (!categoryId.isEmpty())
m_editCategory->setSelectedItem(categoryId);
}
void kMyMoneySplitTable::addToTabOrder(QWidget* w)
{
if (w) {
while (w->focusProxy())
w = w->focusProxy();
m_tabOrderWidgets.append(w);
}
}
bool kMyMoneySplitTable::focusNextPrevChild(bool next)
{
MYMONEYTRACER(tracer);
bool rc = false;
if (isEditMode()) {
QWidget *w = 0;
w = qApp->focusWidget();
int currentWidgetIndex = m_tabOrderWidgets.indexOf(w);
while (w && currentWidgetIndex == -1) {
// qDebug("'%s' not in list, use parent", w->className());
w = w->parentWidget();
currentWidgetIndex = m_tabOrderWidgets.indexOf(w);
}
if (currentWidgetIndex != -1) {
// if(w) qDebug("tab order is at '%s'", w->className());
currentWidgetIndex += next ? 1 : -1;
if (currentWidgetIndex < 0)
currentWidgetIndex = m_tabOrderWidgets.size() - 1;
else if (currentWidgetIndex >= m_tabOrderWidgets.size())
currentWidgetIndex = 0;
w = m_tabOrderWidgets[currentWidgetIndex];
if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) {
// qDebug("Selecting '%s' as focus", w->className());
w->setFocus();
rc = true;
}
}
} else
rc = QTableWidget::focusNextPrevChild(next);
return rc;
}
diff --git a/kmymoney/dialogs/knewaccountdlg.cpp b/kmymoney/dialogs/knewaccountdlg.cpp
index 779c58d20..f241666ad 100644
--- a/kmymoney/dialogs/knewaccountdlg.cpp
+++ b/kmymoney/dialogs/knewaccountdlg.cpp
@@ -1,924 +1,924 @@
/***************************************************************************
knewaccountdlg.cpp
-------------------
copyright : (C) 2000 by Michael Edwardes <mte@users.sourceforge.net>
2004 by Thomas Baumgart <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 "knewaccountdlg.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QPushButton>
#include <QLabel>
#include <QButtonGroup>
#include <QCheckBox>
#include <QTabWidget>
#include <QRadioButton>
#include <QList>
// ----------------------------------------------------------------------------
// KDE Headers
#include <KMessageBox>
#include <KComboBox>
#include <KLed>
#include <kguiutils.h>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneyedit.h"
#include "kmymoneydateinput.h"
#include <mymoneyexception.h>
#include "mymoneyfile.h"
#include "kmymoneyglobalsettings.h"
#include "kmymoneycurrencyselector.h"
#include "knewbankdlg.h"
#include "kmymoneyutils.h"
#include "models.h"
#include "accountsmodel.h"
HierarchyFilterProxyModel::HierarchyFilterProxyModel(QObject *parent)
: AccountsProxyModel(parent)
{
}
/**
* The current account and all it's children are not selectable because the view is used to select a possible parent account.
*/
Qt::ItemFlags HierarchyFilterProxyModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = AccountsProxyModel::flags(index);
QModelIndex currentIndex = index;
while (currentIndex.isValid()) {
QVariant accountId = data(currentIndex, (int)eAccountsModel::Role::ID);
if (accountId.isValid() && accountId.toString() == m_currentAccountId) {
flags = flags & ~Qt::ItemIsSelectable & ~Qt::ItemIsEnabled;
}
currentIndex = currentIndex.parent();
}
return flags;
}
/**
* Set the account for which to select a parent.
*
* @param currentAccountId The current account.
*/
void HierarchyFilterProxyModel::setCurrentAccountId(const QString &currentAccountId)
{
m_currentAccountId = currentAccountId;
}
/**
* Get the index of the selected parent account.
*
* @return The model index of the selected parent account.
*/
QModelIndex HierarchyFilterProxyModel::getSelectedParentAccountIndex() const
{
QModelIndexList list = match(index(0, 0), (int)eAccountsModel::Role::ID, m_currentAccountId, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
if (!list.empty()) {
return list.front().parent();
}
return QModelIndex();
}
/**
* Filter the favorites accounts group.
*/
bool HierarchyFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (!source_parent.isValid()) {
const auto data = sourceModel()->index(source_row, (int)eAccountsModel::Column::Account, source_parent).data((int)eAccountsModel::Role::ID);
if (data.isValid() && data.toString() == AccountsModel::favoritesAccountId)
return false;
}
return AccountsProxyModel::filterAcceptsRow(source_row, source_parent);
}
/**
* Filter all but the first column.
*/
bool HierarchyFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
if (source_column == 0)
return true;
return false;
}
KNewAccountDlg::KNewAccountDlg(const MyMoneyAccount& account, bool isEditing, bool categoryEditor, QWidget *parent, const QString& title)
: KNewAccountDlgDecl(parent),
m_account(account),
m_categoryEditor(categoryEditor),
m_isEditing(isEditing)
{
QString columnName = ((categoryEditor) ? i18n("Categories") : i18n("Accounts"));
MyMoneyFile *file = MyMoneyFile::instance();
// initialize the m_parentAccount member
- QVector<MyMoneyAccount::accountTypeE> filterAccountGroup {m_account.accountGroup()};
+ QVector<eMyMoney::Account> filterAccountGroup {m_account.accountGroup()};
switch (m_account.accountGroup()) {
- case MyMoneyAccount::Asset:
+ case eMyMoney::Account::Asset:
m_parentAccount = file->asset();
break;
- case MyMoneyAccount::Liability:
+ case eMyMoney::Account::Liability:
m_parentAccount = file->liability();
break;
- case MyMoneyAccount::Income:
+ case eMyMoney::Account::Income:
m_parentAccount = file->income();
break;
- case MyMoneyAccount::Expense:
+ case eMyMoney::Account::Expense:
m_parentAccount = file->expense();
break;
- case MyMoneyAccount::Equity:
+ case eMyMoney::Account::Equity:
m_parentAccount = file->equity();
break;
default:
qDebug("Seems we have an account that hasn't been mapped to the top five");
if (m_categoryEditor) {
m_parentAccount = file->income();
- filterAccountGroup[0] = MyMoneyAccount::Income;
+ filterAccountGroup[0] = eMyMoney::Account::Income;
} else {
m_parentAccount = file->asset();
- filterAccountGroup[0] = MyMoneyAccount::Asset;
+ filterAccountGroup[0] = eMyMoney::Account::Asset;
}
}
m_amountGroup->setId(m_grossAmount, 0);
m_amountGroup->setId(m_netAmount, 1);
// the proxy filter model
m_filterProxyModel = new HierarchyFilterProxyModel(this);
m_filterProxyModel->setHideClosedAccounts(true);
m_filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode());
m_filterProxyModel->addAccountGroup(filterAccountGroup);
m_filterProxyModel->setCurrentAccountId(m_account.id());
auto const model = Models::instance()->accountsModel();
m_filterProxyModel->setSourceModel(model);
m_filterProxyModel->setSourceColumns(model->getColumns());
m_filterProxyModel->setDynamicSortFilter(true);
m_parentAccounts->setModel(m_filterProxyModel);
m_parentAccounts->sortByColumn((int)eAccountsModel::Column::Account, Qt::AscendingOrder);
m_subAccountLabel->setText(i18n("Is a sub account"));
accountNameEdit->setText(account.name());
descriptionEdit->setText(account.description());
typeCombo->setEnabled(true);
// load the price mode combo
m_priceMode->insertItem(i18nc("default price mode", "(default)"), 0);
m_priceMode->insertItem(i18n("Price per share"), 1);
m_priceMode->insertItem(i18n("Total for all shares"), 2);
int priceMode = 0;
- if (m_account.accountType() == MyMoneyAccount::Investment) {
+ if (m_account.accountType() == eMyMoney::Account::Investment) {
m_priceMode->setEnabled(true);
if (!m_account.value("priceMode").isEmpty())
priceMode = m_account.value("priceMode").toInt();
}
m_priceMode->setCurrentItem(priceMode);
bool haveMinBalance = false;
bool haveMaxCredit = false;
if (!m_account.openingDate().isValid()) {
m_account.setOpeningDate(KMyMoneyGlobalSettings::firstFiscalDate());
}
m_openingDateEdit->setDate(m_account.openingDate());
handleOpeningBalanceCheckbox(m_account.currencyId());
if (categoryEditor) {
// get rid of the tabs that are not used for categories
int tab = m_tab->indexOf(m_institutionTab);
if (tab != -1)
m_tab->removeTab(tab);
tab = m_tab->indexOf(m_limitsTab);
if (tab != -1)
m_tab->removeTab(tab);
//m_qlistviewParentAccounts->setEnabled(true);
accountNoEdit->setEnabled(false);
m_institutionBox->hide();
m_qcheckboxNoVat->hide();
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Income));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Expense));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Income));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Expense));
// Hardcoded but acceptable - if above we set the default to income do the same here
switch (account.accountType()) {
- case MyMoneyAccount::Expense:
- typeCombo->setCurrentItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Expense), false);
+ case eMyMoney::Account::Expense:
+ typeCombo->setCurrentItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Expense), false);
break;
- case MyMoneyAccount::Income:
+ case eMyMoney::Account::Income:
default:
- typeCombo->setCurrentItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Income), false);
+ typeCombo->setCurrentItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Income), false);
break;
}
m_currency->setEnabled(true);
if (m_isEditing) {
typeCombo->setEnabled(false);
m_currency->setDisabled(MyMoneyFile::instance()->isReferenced(m_account));
}
m_qcheckboxPreferred->hide();
m_qcheckboxTax->setChecked(account.value("Tax").toLower() == "yes");
m_costCenterRequiredCheckBox->setChecked(account.isCostCenterRequired());
loadVatAccounts();
} else {
// get rid of the tabs that are not used for accounts
int taxtab = m_tab->indexOf(m_taxTab);
if (taxtab != -1) {
m_vatCategory->setText(i18n("VAT account"));
m_qcheckboxTax->setChecked(account.value("Tax") == "Yes");
loadVatAccounts();
} else {
m_tab->removeTab(taxtab);
}
m_costCenterRequiredCheckBox->hide();
switch (m_account.accountType()) {
- case MyMoneyAccount::Savings:
- case MyMoneyAccount::Cash:
+ case eMyMoney::Account::Savings:
+ case eMyMoney::Account::Cash:
haveMinBalance = true;
break;
- case MyMoneyAccount::Checkings:
+ case eMyMoney::Account::Checkings:
haveMinBalance = true;
haveMaxCredit = true;
break;
- case MyMoneyAccount::CreditCard:
+ case eMyMoney::Account::CreditCard:
haveMaxCredit = true;
break;
default:
// no limit available, so we might get rid of the tab
int tab = m_tab->indexOf(m_limitsTab);
if (tab != -1)
m_tab->removeTab(tab);
// don't try to hide the widgets we just wiped
// in the next step
haveMaxCredit = haveMinBalance = true;
break;
}
if (!haveMaxCredit) {
m_maxCreditLabel->setEnabled(false);
m_maxCreditLabel->hide();
m_maxCreditEarlyEdit->hide();
m_maxCreditAbsoluteEdit->hide();
}
if (!haveMinBalance) {
m_minBalanceLabel->setEnabled(false);
m_minBalanceLabel->hide();
m_minBalanceEarlyEdit->hide();
m_minBalanceAbsoluteEdit->hide();
}
QString typeString = KMyMoneyUtils::accountTypeToString(account.accountType());
if (m_isEditing) {
if (account.isLiquidAsset()) {
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Checkings));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Savings));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Cash));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Checkings));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Savings));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Cash));
} else {
typeCombo->addItem(typeString);
// Once created, accounts of other account types are not
// allowed to be changed.
typeCombo->setEnabled(false);
}
// Once created, a currency cannot be changed if it is referenced.
m_currency->setDisabled(MyMoneyFile::instance()->isReferenced(m_account));
} else {
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Checkings));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Savings));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Cash));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::CreditCard));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Loan));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Investment));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Asset));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Liability));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Stock));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Checkings));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Savings));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Cash));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::CreditCard));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Loan));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Investment));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Asset));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Liability));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Stock));
/*
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::CertificateDep));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::MoneyMarket));
- typeCombo->addItem(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Currency));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::CertificateDep));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::MoneyMarket));
+ typeCombo->addItem(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Currency));
*/
// Do not create account types that are not supported
// by the current engine.
- if (account.accountType() == MyMoneyAccount::UnknownAccountType ||
- account.accountType() == MyMoneyAccount::CertificateDep ||
- account.accountType() == MyMoneyAccount::MoneyMarket ||
- account.accountType() == MyMoneyAccount::Currency)
- typeString = KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Checkings);
+ if (account.accountType() == eMyMoney::Account::Unknown ||
+ account.accountType() == eMyMoney::Account::CertificateDep ||
+ account.accountType() == eMyMoney::Account::MoneyMarket ||
+ account.accountType() == eMyMoney::Account::Currency)
+ typeString = KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Checkings);
}
typeCombo->setCurrentItem(typeString, false);
if (account.isInvest())
m_institutionBox->hide();
accountNoEdit->setText(account.number());
m_qcheckboxPreferred->setChecked(account.value("PreferredAccount") == "Yes");
m_qcheckboxNoVat->setChecked(account.value("NoVat") == "Yes");
loadKVP("iban", ibanEdit);
loadKVP("minBalanceAbsolute", m_minBalanceAbsoluteEdit);
loadKVP("minBalanceEarly", m_minBalanceEarlyEdit);
loadKVP("maxCreditAbsolute", m_maxCreditAbsoluteEdit);
loadKVP("maxCreditEarly", m_maxCreditEarlyEdit);
// reverse the sign for display purposes
if (!m_maxCreditAbsoluteEdit->lineedit()->text().isEmpty())
m_maxCreditAbsoluteEdit->setValue(m_maxCreditAbsoluteEdit->value()*MyMoneyMoney::MINUS_ONE);
if (!m_maxCreditEarlyEdit->lineedit()->text().isEmpty())
m_maxCreditEarlyEdit->setValue(m_maxCreditEarlyEdit->value()*MyMoneyMoney::MINUS_ONE);
loadKVP("lastNumberUsed", m_lastCheckNumberUsed);
if (m_account.isInvest()) {
typeCombo->setEnabled(false);
m_qcheckboxPreferred->hide();
m_currencyText->hide();
m_currency->hide();
} else {
// use the old field and override a possible new value
if (!MyMoneyMoney(account.value("minimumBalance")).isZero()) {
m_minBalanceAbsoluteEdit->setValue(MyMoneyMoney(account.value("minimumBalance")));
}
}
// m_qcheckboxTax->hide(); TODO should only be visible for VAT category/account
}
m_currency->setSecurity(file->currency(account.currencyId()));
// Load the institutions
// then the accounts
QString institutionName;
try {
if (m_isEditing && !account.institutionId().isEmpty())
institutionName = file->institution(account.institutionId()).name();
else
institutionName.clear();
} catch (const MyMoneyException &e) {
qDebug("exception in init for account dialog: %s", qPrintable(e.what()));
}
if (m_account.isInvest())
m_parentAccounts->setEnabled(false);
if (!categoryEditor)
slotLoadInstitutions(institutionName);
accountNameEdit->setFocus();
if (!title.isEmpty())
setWindowTitle(title);
connect(buttonBox, SIGNAL(rejected()), SLOT(reject()));
connect(buttonBox, SIGNAL(accepted()), this, SLOT(okClicked()));
connect(m_parentAccounts->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection)));
connect(m_qbuttonNew, SIGNAL(clicked()), this, SLOT(slotNewClicked()));
connect(typeCombo, SIGNAL(activated(QString)),
this, SLOT(slotAccountTypeChanged(QString)));
connect(accountNameEdit, SIGNAL(textChanged(QString)), this, SLOT(slotCheckFinished()));
connect(m_vatCategory, SIGNAL(toggled(bool)), this, SLOT(slotVatChanged(bool)));
connect(m_vatAssignment, SIGNAL(toggled(bool)), this, SLOT(slotVatAssignmentChanged(bool)));
connect(m_vatCategory, SIGNAL(toggled(bool)), this, SLOT(slotCheckFinished()));
connect(m_vatAssignment, SIGNAL(toggled(bool)), this, SLOT(slotCheckFinished()));
connect(m_vatRate, SIGNAL(textChanged(QString)), this, SLOT(slotCheckFinished()));
connect(m_vatAccount, SIGNAL(stateChanged()), this, SLOT(slotCheckFinished()));
connect(m_currency, SIGNAL(activated(int)), this, SLOT(slotCheckCurrency()));
connect(m_minBalanceEarlyEdit, SIGNAL(valueChanged(QString)), this, SLOT(slotAdjustMinBalanceAbsoluteEdit(QString)));
connect(m_minBalanceAbsoluteEdit, SIGNAL(valueChanged(QString)), this, SLOT(slotAdjustMinBalanceEarlyEdit(QString)));
connect(m_maxCreditEarlyEdit, SIGNAL(valueChanged(QString)), this, SLOT(slotAdjustMaxCreditAbsoluteEdit(QString)));
connect(m_maxCreditAbsoluteEdit, SIGNAL(valueChanged(QString)), this, SLOT(slotAdjustMaxCreditEarlyEdit(QString)));
connect(m_qcomboboxInstitutions, SIGNAL(activated(QString)), this, SLOT(slotLoadInstitutions(QString)));
QModelIndex parentIndex = m_filterProxyModel->getSelectedParentAccountIndex();
m_parentAccounts->expand(parentIndex);
m_parentAccounts->selectionModel()->select(parentIndex, QItemSelectionModel::SelectCurrent);
m_parentAccounts->scrollTo(parentIndex, QAbstractItemView::PositionAtTop);
m_vatCategory->setChecked(false);
m_vatAssignment->setChecked(false);
// make sure our account does not have an id and no parent assigned
// and certainly no children in case we create a new account
if (!m_isEditing) {
m_account.clearId();
m_account.setParentAccountId(QString());
QStringList::ConstIterator it;
while ((it = m_account.accountList().begin()) != m_account.accountList().end())
m_account.removeAccountId(*it);
} else {
if (!m_account.value("VatRate").isEmpty()) {
m_vatCategory->setChecked(true);
m_vatRate->setValue(MyMoneyMoney(m_account.value("VatRate"))*MyMoneyMoney(100, 1));
} else {
if (!m_account.value("VatAccount").isEmpty()) {
QString accId = m_account.value("VatAccount").toLatin1();
try {
// make sure account exists
MyMoneyFile::instance()->account(accId);
m_vatAssignment->setChecked(true);
m_vatAccount->setSelected(accId);
m_grossAmount->setChecked(true);
if (m_account.value("VatAmount") == "Net")
m_netAmount->setChecked(true);
} catch (const MyMoneyException &) {
}
}
}
}
slotVatChanged(m_vatCategory->isChecked());
slotVatAssignmentChanged(m_vatAssignment->isChecked());
slotCheckFinished();
kMandatoryFieldGroup* requiredFields = new kMandatoryFieldGroup(this);
requiredFields->setOkButton(buttonBox->button(QDialogButtonBox::Ok)); // button to be enabled when all fields present
requiredFields->add(accountNameEdit);
}
void KNewAccountDlg::setOpeningBalance(const MyMoneyMoney& balance)
{
m_openingBalanceEdit->setValue(balance);
}
void KNewAccountDlg::setOpeningBalanceShown(bool shown)
{
m_openingBalanceLabel->setVisible(shown);
m_openingBalanceEdit->setVisible(shown);
}
void KNewAccountDlg::setOpeningDateShown(bool shown)
{
m_openingDateLabel->setVisible(shown);
m_openingDateEdit->setVisible(shown);
}
void KNewAccountDlg::okClicked()
{
MyMoneyFile* file = MyMoneyFile::instance();
QString accountNameText = accountNameEdit->text();
if (accountNameText.isEmpty()) {
KMessageBox::error(this, i18n("You have not specified a name.\nPlease fill in this field."));
accountNameEdit->setFocus();
return;
}
MyMoneyAccount parent = parentAccount();
if (parent.name().length() == 0) {
KMessageBox::error(this, i18n("Please select a parent account."));
return;
}
if (!m_categoryEditor) {
QString institutionNameText = m_qcomboboxInstitutions->currentText();
if (institutionNameText != i18n("(No Institution)")) {
try {
MyMoneyFile *file = MyMoneyFile::instance();
QList<MyMoneyInstitution> list = file->institutionList();
QList<MyMoneyInstitution>::ConstIterator institutionIterator;
for (institutionIterator = list.constBegin(); institutionIterator != list.constEnd(); ++institutionIterator) {
if ((*institutionIterator).name() == institutionNameText)
m_account.setInstitutionId((*institutionIterator).id());
}
} catch (const MyMoneyException &e) {
qDebug("Exception in account institution set: %s", qPrintable(e.what()));
}
} else {
m_account.setInstitutionId(QString());
}
}
m_account.setName(accountNameText);
m_account.setNumber(accountNoEdit->text());
storeKVP("iban", ibanEdit);
storeKVP("minBalanceAbsolute", m_minBalanceAbsoluteEdit);
storeKVP("minBalanceEarly", m_minBalanceEarlyEdit);
// the figures for credit line with reversed sign
if (!m_maxCreditAbsoluteEdit->lineedit()->text().isEmpty())
m_maxCreditAbsoluteEdit->setValue(m_maxCreditAbsoluteEdit->value()*MyMoneyMoney::MINUS_ONE);
if (!m_maxCreditEarlyEdit->lineedit()->text().isEmpty())
m_maxCreditEarlyEdit->setValue(m_maxCreditEarlyEdit->value()*MyMoneyMoney::MINUS_ONE);
storeKVP("maxCreditAbsolute", m_maxCreditAbsoluteEdit);
storeKVP("maxCreditEarly", m_maxCreditEarlyEdit);
if (!m_maxCreditAbsoluteEdit->lineedit()->text().isEmpty())
m_maxCreditAbsoluteEdit->setValue(m_maxCreditAbsoluteEdit->value()*MyMoneyMoney::MINUS_ONE);
if (!m_maxCreditEarlyEdit->lineedit()->text().isEmpty())
m_maxCreditEarlyEdit->setValue(m_maxCreditEarlyEdit->value()*MyMoneyMoney::MINUS_ONE);
storeKVP("lastNumberUsed", m_lastCheckNumberUsed);
// delete a previous version of the minimumbalance information
storeKVP("minimumBalance", QString(), QString());
- MyMoneyAccount::accountTypeE acctype;
+ eMyMoney::Account acctype;
if (!m_categoryEditor) {
acctype = KMyMoneyUtils::stringToAccountType(typeCombo->currentText());
// If it's a loan, check if the parent is asset or liability. In
// case of asset, we change the account type to be AssetLoan
- if (acctype == MyMoneyAccount::Loan
- && parent.accountGroup() == MyMoneyAccount::Asset)
- acctype = MyMoneyAccount::AssetLoan;
+ if (acctype == eMyMoney::Account::Loan
+ && parent.accountGroup() == eMyMoney::Account::Asset)
+ acctype = eMyMoney::Account::AssetLoan;
} else {
acctype = parent.accountGroup();
QString newName;
if (!MyMoneyFile::instance()->isStandardAccount(parent.id())) {
newName = MyMoneyFile::instance()->accountToCategory(parent.id()) + MyMoneyFile::AccountSeperator;
}
newName += accountNameText;
if (!file->categoryToAccount(newName, acctype).isEmpty()
&& (file->categoryToAccount(newName, acctype) != m_account.id())) {
KMessageBox::error(this, QString("<qt>") + i18n("A category named <b>%1</b> already exists. You cannot create a second category with the same name.", newName) + QString("</qt>"));
return;
}
}
m_account.setAccountType(acctype);
m_account.setDescription(descriptionEdit->toPlainText());
m_account.setOpeningDate(m_openingDateEdit->date());
if (!m_categoryEditor) {
m_account.setCurrencyId(m_currency->security().id());
storeKVP("PreferredAccount", m_qcheckboxPreferred);
storeKVP("NoVat", m_qcheckboxNoVat);
if (m_minBalanceAbsoluteEdit->isVisible()) {
m_account.setValue("minimumBalance", m_minBalanceAbsoluteEdit->value().toString());
}
} else {
if (KMyMoneyGlobalSettings::hideUnusedCategory() && !m_isEditing) {
KMessageBox::information(this, i18n("You have selected to suppress the display of unused categories in the KMyMoney configuration dialog. The category you just created will therefore only be shown if it is used. Otherwise, it will be hidden in the accounts/categories view."), i18n("Hidden categories"), "NewHiddenCategory");
}
m_account.setCostCenterRequired(m_costCenterRequiredCheckBox->isChecked());
}
storeKVP("Tax", m_qcheckboxTax);
if (m_qcheckboxOpeningBalance->isChecked())
m_account.setValue("OpeningBalanceAccount", "Yes");
else
m_account.deletePair("OpeningBalanceAccount");
m_account.deletePair("VatAccount");
m_account.deletePair("VatAmount");
m_account.deletePair("VatRate");
if (m_vatCategory->isChecked()) {
m_account.setValue("VatRate", (m_vatRate->value().abs() / MyMoneyMoney(100, 1)).toString());
} else {
if (m_vatAssignment->isChecked() && !m_vatAccount->selectedItems().isEmpty()) {
m_account.setValue("VatAccount", m_vatAccount->selectedItems().first());
if (m_netAmount->isChecked())
m_account.setValue("VatAmount", "Net");
}
}
accept();
}
void KNewAccountDlg::loadKVP(const QString& key, kMyMoneyEdit* widget)
{
if (!widget)
return;
if (m_account.value(key).isEmpty()) {
widget->clearText();
} else {
widget->setValue(MyMoneyMoney(m_account.value(key)));
}
}
void KNewAccountDlg::loadKVP(const QString& key, KLineEdit* widget)
{
if (!widget)
return;
widget->setText(m_account.value(key));
}
void KNewAccountDlg::storeKVP(const QString& key, const QString& text, const QString& value)
{
if (text.isEmpty())
m_account.deletePair(key);
else
m_account.setValue(key, value);
}
void KNewAccountDlg::storeKVP(const QString& key, QCheckBox* widget)
{
if (widget) {
if(widget->isChecked()) {
m_account.setValue(key, "Yes");;
} else {
m_account.deletePair(key);
}
}
}
void KNewAccountDlg::storeKVP(const QString& key, kMyMoneyEdit* widget)
{
storeKVP(key, widget->lineedit()->text(), widget->text());
}
void KNewAccountDlg::storeKVP(const QString& key, KLineEdit* widget)
{
storeKVP(key, widget->text(), widget->text());
}
const MyMoneyAccount& KNewAccountDlg::account()
{
// assign the right currency to the account
m_account.setCurrencyId(m_currency->security().id());
// and the price mode
switch (m_priceMode->currentItem()) {
case 0:
m_account.deletePair("priceMode");
break;
case 1:
case 2:
m_account.setValue("priceMode", QString("%1").arg(m_priceMode->currentItem()));
break;
}
return m_account;
}
const MyMoneyAccount& KNewAccountDlg::parentAccount()
{
return m_parentAccount;
}
void KNewAccountDlg::slotSelectionChanged(const QItemSelection &current, const QItemSelection &previous)
{
Q_UNUSED(previous)
if (!current.indexes().empty()) {
QVariant account = m_parentAccounts->model()->data(current.indexes().front(), (int)eAccountsModel::Role::Account);
if (account.isValid()) {
m_parentAccount = account.value<MyMoneyAccount>();
m_subAccountLabel->setText(i18n("Is a sub account of %1", m_parentAccount.name()));
}
}
}
void KNewAccountDlg::loadVatAccounts()
{
QList<MyMoneyAccount> list;
MyMoneyFile::instance()->accountList(list);
QList<MyMoneyAccount>::Iterator it;
QStringList loadListExpense;
QStringList loadListIncome;
QStringList loadListAsset;
QStringList loadListLiability;
for (it = list.begin(); it != list.end(); ++it) {
if (!(*it).value("VatRate").isEmpty()) {
- if ((*it).accountType() == MyMoneyAccount::Expense)
+ if ((*it).accountType() == eMyMoney::Account::Expense)
loadListExpense += (*it).id();
- else if ((*it).accountType() == MyMoneyAccount::Income)
+ else if ((*it).accountType() == eMyMoney::Account::Income)
loadListIncome += (*it).id();
- else if ((*it).accountType() == MyMoneyAccount::Asset)
+ else if ((*it).accountType() == eMyMoney::Account::Asset)
loadListAsset += (*it).id();
- else if ((*it).accountType() == MyMoneyAccount::Liability)
+ else if ((*it).accountType() == eMyMoney::Account::Liability)
loadListLiability += (*it).id();
}
}
AccountSet vatSet;
if (!loadListAsset.isEmpty())
vatSet.load(m_vatAccount, i18n("Asset"), loadListAsset, true);
if (!loadListLiability.isEmpty())
vatSet.load(m_vatAccount, i18n("Liability"), loadListLiability, false);
if (!loadListIncome.isEmpty())
vatSet.load(m_vatAccount, i18n("Income"), loadListIncome, false);
if (!loadListExpense.isEmpty())
vatSet.load(m_vatAccount, i18n("Expense"), loadListExpense, false);
}
void KNewAccountDlg::slotLoadInstitutions(const QString& name)
{
m_qcomboboxInstitutions->clear();
QString bic;
// Are we forcing the user to use institutions?
m_qcomboboxInstitutions->addItem(i18n("(No Institution)"));
m_bicValue->setText(" ");
ibanEdit->setEnabled(false);
accountNoEdit->setEnabled(false);
try {
MyMoneyFile *file = MyMoneyFile::instance();
QList<MyMoneyInstitution> list = file->institutionList();
QList<MyMoneyInstitution>::ConstIterator institutionIterator;
for (institutionIterator = list.constBegin(); institutionIterator != list.constEnd(); ++institutionIterator) {
if ((*institutionIterator).name() == name) {
ibanEdit->setEnabled(true);
accountNoEdit->setEnabled(true);
m_bicValue->setText((*institutionIterator).value("bic"));
}
m_qcomboboxInstitutions->addItem((*institutionIterator).name());
}
m_qcomboboxInstitutions->setCurrentItem(name, false);
} catch (const MyMoneyException &e) {
qDebug("Exception in institution load: %s", qPrintable(e.what()));
}
}
void KNewAccountDlg::slotNewClicked()
{
MyMoneyInstitution institution;
QPointer<KNewBankDlg> dlg = new KNewBankDlg(institution, this);
if (dlg->exec()) {
MyMoneyFileTransaction ft;
try {
MyMoneyFile *file = MyMoneyFile::instance();
institution = dlg->institution();
file->addInstitution(institution);
ft.commit();
slotLoadInstitutions(institution.name());
} catch (const MyMoneyException &) {
KMessageBox::information(this, i18n("Cannot add institution"));
}
}
delete dlg;
}
void KNewAccountDlg::slotAccountTypeChanged(const QString& typeStr)
{
- MyMoneyAccount::accountTypeE type;
- MyMoneyAccount::accountTypeE oldType;
+ eMyMoney::Account type;
+ eMyMoney::Account oldType;
type = KMyMoneyUtils::stringToAccountType(typeStr);
try {
oldType = m_account.accountType();
if (oldType != type) {
m_account.setAccountType(type);
// update the account group displayed in the accounts hierarchy
m_filterProxyModel->clear();
- m_filterProxyModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {m_account.accountGroup()});
+ m_filterProxyModel->addAccountGroup(QVector<eMyMoney::Account> {m_account.accountGroup()});
}
} catch (const MyMoneyException &) {
qWarning("Unexpected exception in KNewAccountDlg::slotAccountTypeChanged()");
}
}
void KNewAccountDlg::slotCheckFinished()
{
bool showButton = true;
if (accountNameEdit->text().length() == 0) {
showButton = false;
}
if (m_vatCategory->isChecked() && m_vatRate->value() <= MyMoneyMoney()) {
showButton = false;
} else {
if (m_vatAssignment->isChecked() && m_vatAccount->selectedItems().isEmpty())
showButton = false;
}
buttonBox->button(QDialogButtonBox::Ok)->setEnabled(showButton);
}
void KNewAccountDlg::slotVatChanged(bool state)
{
if (state) {
m_vatCategoryFrame->show();
m_vatAssignmentFrame->hide();
} else {
m_vatCategoryFrame->hide();
if (!m_account.isAssetLiability()) {
m_vatAssignmentFrame->show();
}
}
}
void KNewAccountDlg::slotVatAssignmentChanged(bool state)
{
m_vatAccount->setEnabled(state);
m_amountGroupBox->setEnabled(state);
}
void KNewAccountDlg::adjustEditWidgets(kMyMoneyEdit* dst, kMyMoneyEdit* src, char mode, int corr)
{
MyMoneyMoney factor(corr, 1);
- if (m_account.accountGroup() == MyMoneyAccount::Asset)
+ if (m_account.accountGroup() == eMyMoney::Account::Asset)
factor = -factor;
switch (mode) {
case '<':
if (src->value()*factor < dst->value()*factor)
dst->setValue(src->value());
break;
case '>':
if (src->value()*factor > dst->value()*factor)
dst->setValue(src->value());
break;
}
}
void KNewAccountDlg::handleOpeningBalanceCheckbox(const QString &currencyId)
{
- if (m_account.accountType() == MyMoneyAccount::Equity) {
+ if (m_account.accountType() == eMyMoney::Account::Equity) {
// check if there is another opening balance account with the same currency
bool isOtherOpenBalancingAccount = false;
QList<MyMoneyAccount> list;
MyMoneyFile::instance()->accountList(list);
QList<MyMoneyAccount>::Iterator it;
for (it = list.begin(); it != list.end(); ++it) {
if (it->id() == m_account.id() || currencyId != it->currencyId()
- || it->accountType() != MyMoneyAccount::Equity)
+ || it->accountType() != eMyMoney::Account::Equity)
continue;
if (it->value("OpeningBalanceAccount") == "Yes") {
isOtherOpenBalancingAccount = true;
break;
}
}
if (!isOtherOpenBalancingAccount) {
bool isOpenBalancingAccount = m_account.value("OpeningBalanceAccount") == "Yes";
m_qcheckboxOpeningBalance->setChecked(isOpenBalancingAccount);
if (isOpenBalancingAccount) {
// let only allow state change if no transactions are assigned to this account
bool hasTransactions = MyMoneyFile::instance()->transactionCount(m_account.id()) != 0;
m_qcheckboxOpeningBalance->setEnabled(!hasTransactions);
if (hasTransactions)
m_qcheckboxOpeningBalance->setToolTip(i18n("Option has been disabled because there are transactions assigned to this account"));
}
} else {
m_qcheckboxOpeningBalance->setChecked(false);
m_qcheckboxOpeningBalance->setEnabled(false);
m_qcheckboxOpeningBalance->setToolTip(i18n("Option has been disabled because there is another account flagged to be an opening balance account for this currency"));
}
} else {
m_qcheckboxOpeningBalance->setVisible(false);
}
}
void KNewAccountDlg::slotAdjustMinBalanceAbsoluteEdit(const QString&)
{
adjustEditWidgets(m_minBalanceAbsoluteEdit, m_minBalanceEarlyEdit, '<', -1);
}
void KNewAccountDlg::slotAdjustMinBalanceEarlyEdit(const QString&)
{
adjustEditWidgets(m_minBalanceEarlyEdit, m_minBalanceAbsoluteEdit, '>', -1);
}
void KNewAccountDlg::slotAdjustMaxCreditAbsoluteEdit(const QString&)
{
adjustEditWidgets(m_maxCreditAbsoluteEdit, m_maxCreditEarlyEdit, '>', 1);
}
void KNewAccountDlg::slotAdjustMaxCreditEarlyEdit(const QString&)
{
adjustEditWidgets(m_maxCreditEarlyEdit, m_maxCreditAbsoluteEdit, '<', 1);
}
void KNewAccountDlg::slotCheckCurrency()
{
handleOpeningBalanceCheckbox(m_currency->security().id());
}
void KNewAccountDlg::addTab(QWidget* w, const QString& name)
{
if (w) {
w->setParent(m_tab);
m_tab->addTab(w, name);
}
}
diff --git a/kmymoney/dialogs/settings/ksettingsgeneral.cpp b/kmymoney/dialogs/settings/ksettingsgeneral.cpp
index 05b27fe0c..2bacfce2f 100644
--- a/kmymoney/dialogs/settings/ksettingsgeneral.cpp
+++ b/kmymoney/dialogs/settings/ksettingsgeneral.cpp
@@ -1,110 +1,112 @@
/***************************************************************************
ksettingsgeneral.cpp
--------------------
copyright : (C) 2005 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 "ksettingsgeneral.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QButtonGroup>
#include <QFileDialog>
#include <QLabel>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneydateinput.h"
#include "models.h"
#include "accountsmodel.h"
+#include "mymoneyfile.h"
+#include "mymoneyaccount.h"
KSettingsGeneral::KSettingsGeneral(QWidget* parent) :
KSettingsGeneralDecl(parent)
{
// hide the internally used date field
kcfg_StartDate->hide();
// setup connections, so that the sort optios get loaded once the edit fields are filled
connect(kcfg_StartDate, SIGNAL(dateChanged(QDate)), this, SLOT(slotLoadStartDate(QDate)));
// setup connections, so that changes by the user are forwarded to the (hidden) edit fields
connect(m_startDateEdit, SIGNAL(dateChanged(QDate)), kcfg_StartDate, SLOT(setDate(QDate)));
connect(choosePath, SIGNAL(pressed()), this, SLOT(slotChooseLogPath()));
initialHideZeroBalanceEquities = kcfg_HideZeroBalanceEquities->isChecked();
}
KSettingsGeneral::~KSettingsGeneral()
{
}
void KSettingsGeneral::slotChooseLogPath()
{
QString filePath = QFileDialog::getExistingDirectory(this, i18n("Choose file path"), QDir::homePath());
kcfg_logPath->setText(filePath);
slotUpdateLogTypes();
}
void KSettingsGeneral::slotLoadStartDate(const QDate&)
{
// only need this once
disconnect(kcfg_StartDate, SIGNAL(dateChanged(QDate)), this, SLOT(slotLoadStartDate(QDate)));
m_startDateEdit->setDate(kcfg_StartDate->date());
}
void KSettingsGeneral::slotUpdateLogTypes()
{
bool enable = kcfg_logPath->text().isEmpty() ? false : true;
kcfg_logImportedStatements->setEnabled(enable);
kcfg_logOfxTransactions->setEnabled(enable);
if (!enable)
{
kcfg_logImportedStatements->setChecked(enable);
kcfg_logOfxTransactions->setChecked(enable);
}
}
void KSettingsGeneral::showEvent(QShowEvent *event)
{
KSettingsGeneralDecl::showEvent(event);
slotUpdateLogTypes();
}
void KSettingsGeneral::slotUpdateEquitiesVisibility()
{
if (initialHideZeroBalanceEquities == kcfg_HideZeroBalanceEquities->isChecked()) // setting hasn't been changed, so return
return;
initialHideZeroBalanceEquities = kcfg_HideZeroBalanceEquities->isChecked();
AccountsModel* accountsModel = Models::instance()->accountsModel(); // items' model for accounts' page
InstitutionsModel* institutionsModel = Models::instance()->institutionsModel(); // items' model for institutions' page
MyMoneyFile *file = MyMoneyFile::instance();
QList<MyMoneyAccount> accountsList;
file->accountList(accountsList);
foreach (const auto account, accountsList) {
if (account.isInvest() && account.balance().isZero()) { // search only for zero balance stocks
if (initialHideZeroBalanceEquities) {
- accountsModel->slotObjectRemoved(MyMoneyFile::notifyAccount, account.id()); // remove item from accounts' page
- institutionsModel->slotObjectRemoved(MyMoneyFile::notifyAccount, account.id()); // remove item from institutions' page
+ accountsModel->slotObjectRemoved(eMyMoney::File::Object::Account, account.id()); // remove item from accounts' page
+ institutionsModel->slotObjectRemoved(eMyMoney::File::Object::Account, account.id()); // remove item from institutions' page
} else {
- accountsModel->slotObjectAdded(MyMoneyFile::notifyAccount, dynamic_cast<const MyMoneyObject* const>(&account)); // add item to accounts' page
- institutionsModel->slotObjectAdded(MyMoneyFile::notifyAccount, dynamic_cast<const MyMoneyObject* const>(&account)); // add item to institutions' page
+ accountsModel->slotObjectAdded(eMyMoney::File::Object::Account, dynamic_cast<const MyMoneyObject* const>(&account)); // add item to accounts' page
+ institutionsModel->slotObjectAdded(eMyMoney::File::Object::Account, dynamic_cast<const MyMoneyObject* const>(&account)); // add item to institutions' page
}
}
}
}
diff --git a/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp b/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp
index 245c2a4a6..1ea61c595 100644
--- a/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp
+++ b/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp
@@ -1,333 +1,334 @@
/***************************************************************************
ksettingsonlinequotes.cpp
-------------------
begin : Thu Dec 30 2004
copyright : (C) 2004 by Thomas Baumgart
email : Thomas Baumgart <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 "ksettingsonlinequotes.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QCheckBox>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KConfig>
#include <KGuiItem>
#include <KLocalizedString>
#include <KMessageBox>
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoney/converter/webpricequote.h"
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
#include "icons/icons.h"
using namespace Icons;
KSettingsOnlineQuotes::KSettingsOnlineQuotes(QWidget *parent)
: KSettingsOnlineQuotesDecl(parent),
m_quoteInEditing(false)
{
QStringList groups = WebPriceQuote::quoteSources();
loadList(true /*updateResetList*/);
m_updateButton->setEnabled(false);
KGuiItem updateButtenItem(i18nc("Accepts the entered data and stores it", "&Update"),
QIcon::fromTheme(g_Icons[Icon::DialogOK]),
i18n("Accepts the entered data and stores it"),
i18n("Use this to accept the modified data."));
KGuiItem::assign(m_updateButton, updateButtenItem);
KGuiItem deleteButtenItem(i18n("&Delete"),
QIcon::fromTheme(g_Icons[Icon::EditDelete]),
i18n("Delete the selected source entry"),
i18n("Use this to delete the selected online source entry"));
KGuiItem::assign(m_deleteButton, deleteButtenItem);
KGuiItem newButtenItem(i18nc("Create a new source entry for online quotes", "&New..."),
QIcon::fromTheme(g_Icons[Icon::DocumentNew]),
i18n("Create a new source entry for online quotes"),
i18n("Use this to create a new entry for online quotes"));
KGuiItem::assign(m_newButton, newButtenItem);
m_editIdentifyBy->addItem(i18n("Symbol"), WebPriceQuoteSource::identifyBy::Symbol);
m_editIdentifyBy->addItem(i18n("Identification number"), WebPriceQuoteSource::identifyBy::IdentificationNumber);
m_editIdentifyBy->addItem(i18n("Name"), WebPriceQuoteSource::identifyBy::Name);
connect(m_dumpCSVProfile, SIGNAL(clicked()), this, SLOT(slotDumpCSVProfile()));
connect(m_updateButton, SIGNAL(clicked()), this, SLOT(slotUpdateEntry()));
connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotNewEntry()));
connect(m_deleteButton, SIGNAL(clicked()), this, SLOT(slotDeleteEntry()));
connect(m_quoteSourceList, SIGNAL(itemSelectionChanged()), this, SLOT(slotLoadWidgets()));
connect(m_quoteSourceList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(slotEntryRenamed(QListWidgetItem*)));
connect(m_quoteSourceList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotStartRename(QListWidgetItem*)));
connect(m_editURL, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
connect(m_editCSVURL, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
connect(m_editIdentifier, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
connect(m_editIdentifyBy, SIGNAL(currentIndexChanged(QString)), this, SLOT(slotEntryChanged()));
connect(m_editDate, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
connect(m_editDateFormat, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
connect(m_editPrice, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged()));
connect(m_skipStripping, SIGNAL(toggled(bool)), this, SLOT(slotEntryChanged()));
}
void KSettingsOnlineQuotes::loadList(const bool updateResetList)
{
//disconnect the slot while items are being loaded and reconnect at the end
disconnect(m_quoteSourceList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(slotEntryRenamed(QListWidgetItem*)));
m_quoteInEditing = false;
QStringList groups = WebPriceQuote::quoteSources();
if (updateResetList)
m_resetList.clear();
m_quoteSourceList->clear();
QStringList::Iterator it;
for (it = groups.begin(); it != groups.end(); ++it) {
QListWidgetItem* item = new QListWidgetItem(*it);
item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
m_quoteSourceList->addItem(item);
if (updateResetList)
m_resetList += WebPriceQuoteSource(*it);
}
m_quoteSourceList->sortItems();
QListWidgetItem* first = m_quoteSourceList->item(0);
if (first)
m_quoteSourceList->setCurrentItem(first);
slotLoadWidgets();
m_newButton->setEnabled((m_quoteSourceList->findItems(i18n("New Quote Source"), Qt::MatchExactly)).count() == 0);
connect(m_quoteSourceList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(slotEntryRenamed(QListWidgetItem*)));
}
void KSettingsOnlineQuotes::resetConfig()
{
QStringList::ConstIterator it;
QStringList groups = WebPriceQuote::quoteSources();
// delete all currently defined entries
for (it = groups.constBegin(); it != groups.constEnd(); ++it) {
WebPriceQuoteSource(*it).remove();
}
// and write back the one's from the reset list
QList<WebPriceQuoteSource>::ConstIterator itr;
for (itr = m_resetList.constBegin(); itr != m_resetList.constEnd(); ++itr) {
(*itr).write();
}
loadList();
}
void KSettingsOnlineQuotes::slotLoadWidgets()
{
m_quoteInEditing = false;
QListWidgetItem* item = m_quoteSourceList->currentItem();
m_editURL->setEnabled(true);
m_editCSVURL->setEnabled(true);
m_editIdentifier->setEnabled(true);
m_editIdentifyBy->setEnabled(true);
m_editPrice->setEnabled(true);
m_editDate->setEnabled(true);
m_editDateFormat->setEnabled(true);
m_skipStripping->setEnabled(true);
m_dumpCSVProfile->setEnabled(true);
m_deleteButton->setEnabled(true);
m_editURL->setText(QString());
m_editCSVURL->setText(QString());
m_editIdentifier->setText(QString());
m_editIdentifyBy->setCurrentIndex(WebPriceQuoteSource::identifyBy::Symbol);
m_editPrice->setText(QString());
m_editDate->setText(QString());
m_editDateFormat->setText(QString());
if (item) {
m_currentItem = WebPriceQuoteSource(item->text());
m_editURL->setText(m_currentItem.m_url);
m_editCSVURL->setText(m_currentItem.m_csvUrl);
m_editIdentifier->setText(m_currentItem.m_webID);
m_editIdentifyBy->setCurrentIndex(m_currentItem.m_webIDBy);
m_editPrice->setText(m_currentItem.m_price);
m_editDate->setText(m_currentItem.m_date);
m_editDateFormat->setText(m_currentItem.m_dateformat);
m_skipStripping->setChecked(m_currentItem.m_skipStripping);
} else {
m_editURL->setEnabled(false);
m_editCSVURL->setEnabled(false);
m_editIdentifier->setEnabled(false);
m_editIdentifyBy->setEnabled(false);
m_editPrice->setEnabled(false);
m_editDate->setEnabled(false);
m_editDateFormat->setEnabled(false);
m_skipStripping->setEnabled(false);
m_dumpCSVProfile->setEnabled(false);
m_deleteButton->setEnabled(false);
}
m_updateButton->setEnabled(false);
}
void KSettingsOnlineQuotes::slotEntryChanged()
{
bool modified = m_editURL->text() != m_currentItem.m_url
|| m_editCSVURL->text() != m_currentItem.m_csvUrl
|| m_editIdentifier->text() != m_currentItem.m_webID
|| m_editIdentifyBy->currentData().toInt() != static_cast<int>(m_currentItem.m_webIDBy)
|| m_editDate->text() != m_currentItem.m_date
|| m_editDateFormat->text() != m_currentItem.m_dateformat
|| m_editPrice->text() != m_currentItem.m_price
|| m_skipStripping->isChecked() != m_currentItem.m_skipStripping;
m_updateButton->setEnabled(modified);
}
void KSettingsOnlineQuotes::slotDumpCSVProfile()
{
KSharedConfigPtr config = CSVImporter::configFile();
PricesProfile profile;
profile.m_profileName = m_currentItem.m_name;
profile.m_profileType = Profile::StockPrices;
bool profileExists = false;
bool writeProfile = true;
if (profile.readSettings(config))
profileExists = true;
else {
profile.m_profileType = Profile::CurrencyPrices;
if (profile.readSettings(config))
profileExists = true;
}
if (profileExists)
writeProfile = (KMessageBox::questionYesNoCancel(this,
i18n("CSV profile <b>%1</b> already exists.<br>"
"Do you want to overwrite it?",
m_currentItem.m_name),
i18n("CSV Profile Already Exists")) == KMessageBox::Yes ? true : false);
if (writeProfile) {
QMap<QString, PricesProfile> quoteSources = WebPriceQuote::defaultCSVQuoteSources();
profile = quoteSources.value(m_currentItem.m_name);
if (profile.m_profileName.compare(m_currentItem.m_name, Qt::CaseInsensitive) == 0) {
profile.writeSettings(config);
CSVImporter::profilesAction(profile.type(), ProfileAction::Add, profile.m_profileName, profile.m_profileName);
}
}
CSVImporter::profilesAction(profile.type(), ProfileAction::UpdateLastUsed, profile.m_profileName, profile.m_profileName);
}
void KSettingsOnlineQuotes::slotUpdateEntry()
{
m_currentItem.m_url = m_editURL->text();
m_currentItem.m_csvUrl = m_editCSVURL->text();
m_currentItem.m_webID = m_editIdentifier->text();
m_currentItem.m_webIDBy = static_cast<WebPriceQuoteSource::identifyBy>(m_editIdentifyBy->currentData().toInt());
m_currentItem.m_date = m_editDate->text();
m_currentItem.m_dateformat = m_editDateFormat->text();
m_currentItem.m_price = m_editPrice->text();
m_currentItem.m_skipStripping = m_skipStripping->isChecked();
m_currentItem.write();
slotEntryChanged();
}
void KSettingsOnlineQuotes::slotNewEntry()
{
WebPriceQuoteSource newSource(i18n("New Quote Source"));
newSource.write();
loadList();
QListWidgetItem* item = m_quoteSourceList->findItems(i18n("New Quote Source"), Qt::MatchExactly).at(0);
if (item) {
m_quoteSourceList->setCurrentItem(item);
slotLoadWidgets();
}
}
void KSettingsOnlineQuotes::slotDeleteEntry()
{
// first check if no security is using this online source
QList<MyMoneySecurity> securities = MyMoneyFile::instance()->securityList();
foreach(const auto security, securities) {
if (security.value(QStringLiteral("kmm-online-source")).compare(m_currentItem.m_name) == 0) {
if (KMessageBox::questionYesNo(this,
i18n("Security <b>%1</b> uses this quote source.<br>"
"Do you really want to remove it?", security.name()),
i18n("Delete quote source")) == KMessageBox::Yes)
break; // webpricequote can handle missing online quotes, so proceed without any extra action
else
return;
}
}
// remove online source from webpricequote...
m_currentItem.remove();
// ...and from setting's list
int row = m_quoteSourceList->currentRow();
QListWidgetItem *item = m_quoteSourceList->takeItem(row);
if (item)
delete item;
item = nullptr;
int count = m_quoteSourceList->count();
if (row < count) // select next available entry...
item = m_quoteSourceList->item(row);
else if (row >= count && count > 0) // ...or last entry if this was the last entry...
item = m_quoteSourceList->item(count - 1);
if (item) {
m_quoteSourceList->setCurrentItem(item);
slotLoadWidgets();
}
}
void KSettingsOnlineQuotes::slotStartRename(QListWidgetItem* item)
{
m_quoteInEditing = true;
m_quoteSourceList->editItem(item);
}
void KSettingsOnlineQuotes::slotEntryRenamed(QListWidgetItem* item)
{
//if there is no current item selected, exit
if (m_quoteInEditing == false || !m_quoteSourceList->currentItem() || item != m_quoteSourceList->currentItem())
return;
m_quoteInEditing = false;
QString text = item->text();
int nameCount = 0;
for (int i = 0; i < m_quoteSourceList->count(); ++i) {
if (m_quoteSourceList->item(i)->text() == text)
++nameCount;
}
// Make sure we get a non-empty and unique name
if (text.length() > 0 && nameCount == 1) {
m_currentItem.rename(text);
} else {
item->setText(m_currentItem.m_name);
}
m_quoteSourceList->sortItems();
m_newButton->setEnabled(m_quoteSourceList->findItems(i18n("New Quote Source"), Qt::MatchExactly).count() == 0);
}
diff --git a/kmymoney/dialogs/transactioneditor.cpp b/kmymoney/dialogs/transactioneditor.cpp
index d8bc826ca..ace9fdbad 100644
--- a/kmymoney/dialogs/transactioneditor.cpp
+++ b/kmymoney/dialogs/transactioneditor.cpp
@@ -1,2291 +1,2293 @@
/***************************************************************************
transactioneditor.cpp
----------
begin : Wed Jun 07 2006
copyright : (C) 2006 by Thomas Baumgart
email : Thomas Baumgart <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 "transactioneditor.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QLabel>
#include <QApplication>
#include <QEventLoop>
#include <QRadioButton>
#include <QKeyEvent>
#include <QList>
#include <QEvent>
#include <QToolTip>
#include <QPushButton>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KTextEdit>
#include <KLocalizedString>
#include <KComboBox>
#include <KMessageBox>
#include <KStandardGuiItem>
#include <KGuiItem>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyutils.h"
#include "kmymoneycategory.h"
#include "kmymoneymvccombo.h"
#include "kmymoneydateinput.h"
#include "kmymoneyedit.h"
#include "kmymoneylineedit.h"
#include <kmymoneyaccountcompletion.h>
#include "kmymoneyaccountselector.h"
#include "mymoneyfile.h"
+#include "mymoneyprice.h"
+#include "mymoneysecurity.h"
#include "kmymoneyutils.h"
#include "transactionform.h"
#include "kmymoneyglobalsettings.h"
#include "ksplittransactiondlg.h"
#include "kcurrencycalculator.h"
#include "kselecttransactionsdlg.h"
#include "icons.h"
using namespace KMyMoneyRegister;
using namespace KMyMoneyTransactionForm;
using namespace Icons;
TransactionEditor::TransactionEditor() :
- m_paymentMethod(MyMoneySchedule::STYPE_ANY),
+ m_paymentMethod(eMyMoney::Schedule::PaymentType::Any),
m_regForm(0),
m_item(0),
m_initialAction(ActionNone),
m_openEditSplits(false),
m_memoChanged(false)
{
}
TransactionEditor::TransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::Transaction* item, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate) :
- m_paymentMethod(MyMoneySchedule::STYPE_ANY),
+ m_paymentMethod(eMyMoney::Schedule::PaymentType::Any),
m_transactions(list),
m_regForm(regForm),
m_item(item),
m_transaction(item->transaction()),
m_split(item->split()),
m_lastPostDate(lastPostDate),
m_initialAction(ActionNone),
m_openEditSplits(false),
m_memoChanged(false)
{
m_item->startEditMode();
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotUpdateAccount()));
}
TransactionEditor::~TransactionEditor()
{
// Make sure the widgets do not send out signals to the editor anymore
// After all, the editor is about to die
//disconnect first tagCombo:
KTagContainer *w = dynamic_cast<KTagContainer*>(haveWidget("tag"));
if (w && w->tagCombo()) {
w->tagCombo()->disconnect(this);
}
QMap<QString, QWidget*>::iterator it_w;
for (it_w = m_editWidgets.begin(); it_w != m_editWidgets.end(); ++it_w) {
(*it_w)->disconnect(this);
}
m_regForm->removeEditWidgets(m_editWidgets);
m_item->leaveEditMode();
emit finishEdit(m_transactions);
}
void TransactionEditor::slotUpdateAccount(const QString& id)
{
m_account = MyMoneyFile::instance()->account(id);
setupPrecision();
}
void TransactionEditor::slotUpdateAccount()
{
// reload m_account as it might have been changed
m_account = MyMoneyFile::instance()->account(m_account.id());
setupPrecision();
}
void TransactionEditor::setupPrecision()
{
const int prec = (m_account.id().isEmpty()) ? 2 : MyMoneyMoney::denomToPrec(m_account.fraction());
QStringList widgets = QString("amount,deposit,payment").split(',');
QStringList::const_iterator it_w;
for (it_w = widgets.constBegin(); it_w != widgets.constEnd(); ++it_w) {
QWidget * w;
if ((w = haveWidget(*it_w)) != 0) {
dynamic_cast<kMyMoneyEdit*>(w)->setPrecision(prec);
}
}
}
void TransactionEditor::setup(QWidgetList& tabOrderWidgets, const MyMoneyAccount& account, KMyMoneyRegister::Action action)
{
m_account = account;
m_initialAction = action;
createEditWidgets();
m_regForm->arrangeEditWidgets(m_editWidgets, m_item);
m_regForm->tabOrder(tabOrderWidgets, m_item);
QWidget* w = haveWidget("tabbar");
if (w) {
tabOrderWidgets.append(w);
TabBar* tabbar = dynamic_cast<TabBar*>(w);
if ((tabbar) && (action == KMyMoneyRegister::ActionNone)) {
action = static_cast<KMyMoneyRegister::Action>(tabbar->currentIndex());
}
}
loadEditWidgets(action);
// remove all unused widgets and don't forget to remove them
// from the tab order list as well
m_editWidgets.removeOrphans();
QWidgetList::iterator it_w;
const QWidgetList editWidgets(m_editWidgets.values());
for (it_w = tabOrderWidgets.begin(); it_w != tabOrderWidgets.end();) {
if (editWidgets.contains(*it_w)) {
++it_w;
} else {
// before we remove the widget, we make sure it's not a part of a known one.
// these could be a direct child in case of KMyMoneyDateInput and KMyMoneyEdit
// where we store the pointer to the surrounding frame in editWidgets
// or the parent is called "KMyMoneyCategoryFrame"
if (*it_w) {
if (editWidgets.contains((*it_w)->parentWidget())
|| ((*it_w)->parentWidget() && (*it_w)->parentWidget()->objectName() == QLatin1String("KMyMoneyCategoryFrame"))) {
++it_w;
} else {
// qDebug("Remove '%s' from taborder", qPrintable((*it_w)->objectName()));
it_w = tabOrderWidgets.erase(it_w);
}
} else {
it_w = tabOrderWidgets.erase(it_w);
}
}
}
clearFinalWidgets();
setupFinalWidgets();
slotUpdateButtonState();
}
void TransactionEditor::clearFinalWidgets()
{
m_finalEditWidgets.clear();
}
void TransactionEditor::addFinalWidget(const QWidget* w)
{
if (w) {
m_finalEditWidgets << w;
}
}
void TransactionEditor::slotReloadEditWidgets()
{
}
bool TransactionEditor::eventFilter(QObject* o, QEvent* e)
{
bool rc = false;
if (o == haveWidget("number")) {
if (e->type() == QEvent::MouseButtonDblClick) {
emit assignNumber();
rc = true;
}
}
// if the object is a widget, the event is a key press event and
// the object is one of our edit widgets, then ....
if (o->isWidgetType()
&& (e->type() == QEvent::KeyPress)
&& m_editWidgets.values().contains(dynamic_cast<QWidget*>(o))) {
QKeyEvent* k = dynamic_cast<QKeyEvent*>(e);
if ((k->modifiers() & Qt::KeyboardModifierMask) == 0
|| (k->modifiers() & Qt::KeypadModifier) != 0) {
bool isFinal = false;
QList<const QWidget*>::const_iterator it_w;
switch (k->key()) {
case Qt::Key_Return:
case Qt::Key_Enter:
// we check, if the object is one of the m_finalEditWidgets and if it's
// a kMyMoneyEdit object that the value is not 0. If any of that is the
// case, it's the final object. In other cases, we convert the enter
// key into a TAB key to move between the fields. Of course, we only need
// to do this as long as the appropriate option is set. In all other cases,
// we treat the return/enter key as such.
if (KMyMoneyGlobalSettings::enterMovesBetweenFields()) {
for (it_w = m_finalEditWidgets.constBegin(); !isFinal && it_w != m_finalEditWidgets.constEnd(); ++it_w) {
if (*it_w == o) {
if (dynamic_cast<const kMyMoneyEdit*>(*it_w)) {
isFinal = !(dynamic_cast<const kMyMoneyEdit*>(*it_w)->value().isZero());
} else
isFinal = true;
}
}
} else
isFinal = true;
// for the non-final objects, we treat the return key as a TAB
if (!isFinal) {
QKeyEvent evt(e->type(),
Qt::Key_Tab, k->modifiers(), QString(),
k->isAutoRepeat(), k->count());
QApplication::sendEvent(o, &evt);
// in case of a category item and the split button is visible
// send a second event so that we get passed the button.
if (dynamic_cast<KMyMoneyCategory*>(o) && dynamic_cast<KMyMoneyCategory*>(o)->splitButton())
QApplication::sendEvent(o, &evt);
} else {
QTimer::singleShot(0, this, SIGNAL(returnPressed()));
}
// don't process any further
rc = true;
break;
case Qt::Key_Escape:
QTimer::singleShot(0, this, SIGNAL(escapePressed()));
break;
}
}
}
return rc;
}
void TransactionEditor::slotNumberChanged(const QString& txt)
{
QString next = txt;
kMyMoneyLineEdit* number = dynamic_cast<kMyMoneyLineEdit*>(haveWidget("number"));
QString schedInfo;
if (!m_scheduleInfo.isEmpty()) {
schedInfo = i18n("<center>Processing schedule for %1.</center>", m_scheduleInfo);
}
while (MyMoneyFile::instance()->checkNoUsed(m_account.id(), next)) {
if (KMessageBox::questionYesNo(m_regForm, QString("<qt>") + schedInfo + i18n("<center>Check number <b>%1</b> has already been used in account <b>%2</b>.</center>"
"<center>Do you want to replace it with the next available number?</center>", next, m_account.name()) + QString("</qt>"), i18n("Duplicate number")) == KMessageBox::Yes) {
assignNextNumber();
next = KMyMoneyUtils::nextCheckNumber(m_account);
} else {
number->setText(QString());
break;
}
}
}
void TransactionEditor::slotUpdateMemoState()
{
KTextEdit* memo = dynamic_cast<KTextEdit*>(m_editWidgets["memo"]);
if (memo) {
m_memoChanged = (memo->toPlainText() != m_memoText);
}
}
void TransactionEditor::slotUpdateButtonState()
{
QString reason;
emit transactionDataSufficient(isComplete(reason));
}
QWidget* TransactionEditor::haveWidget(const QString& name) const
{
return m_editWidgets.haveWidget(name);
}
int TransactionEditor::slotEditSplits()
{
return QDialog::Rejected;
}
void TransactionEditor::setTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s)
{
m_transaction = t;
m_split = s;
loadEditWidgets();
}
bool TransactionEditor::fixTransactionCommodity(const MyMoneyAccount& account)
{
bool rc = true;
bool firstTimeMultiCurrency = true;
m_account = account;
MyMoneyFile* file = MyMoneyFile::instance();
// determine the max fraction for this account
MyMoneySecurity sec = file->security(m_account.currencyId());
int fract = m_account.fraction();
// scan the list of selected transactions
KMyMoneyRegister::SelectedTransactions::iterator it_t;
for (it_t = m_transactions.begin(); (rc == true) && (it_t != m_transactions.end()); ++it_t) {
// there was a time when the schedule editor did not setup the transaction commodity
// let's give a helping hand here for those old schedules
if ((*it_t).transaction().commodity().isEmpty())
(*it_t).transaction().setCommodity(m_account.currencyId());
// we need to check things only if a different commodity is used
if (m_account.currencyId() != (*it_t).transaction().commodity()) {
MyMoneySecurity osec = file->security((*it_t).transaction().commodity());
switch ((*it_t).transaction().splitCount()) {
case 0:
// new transaction, guess nothing's here yet ;)
break;
case 1:
try {
// make sure, that the value is equal to the shares, don't forget our own copy
MyMoneySplit& splitB = (*it_t).split(); // reference usage wanted here
if (m_split == splitB)
m_split.setValue(splitB.shares());
splitB.setValue(splitB.shares());
(*it_t).transaction().modifySplit(splitB);
} catch (const MyMoneyException &e) {
qDebug("Unable to update commodity to second splits currency in %s: '%s'", qPrintable((*it_t).transaction().id()), qPrintable(e.what()));
}
break;
case 2:
// If we deal with multiple currencies we make sure, that for
// transactions with two splits, the transaction's commodity is the
// currency of the currently selected account. This saves us from a
// lot of grieve later on. We just have to switch the
// transactions commodity. Let's assume the following scenario:
// - transactions commodity is CA
// - splitB and account's currencyId is CB
// - splitA is of course in CA (otherwise we have a real problem)
// - Value is V in both splits
// - Shares in splitB is SB
// - Shares in splitA is SA (and equal to V)
//
// We do the following:
// - change transactions commodity to CB
// - set V in both splits to SB
// - modify the splits in the transaction
try {
// retrieve the splits
MyMoneySplit& splitB = (*it_t).split(); // reference usage wanted here
MyMoneySplit splitA = (*it_t).transaction().splitByAccount(m_account.id(), false);
// - set V in both splits to SB. Don't forget our own copy
if (m_split == splitB) {
m_split.setValue(splitB.shares());
}
splitB.setValue(splitB.shares());
splitA.setValue(-splitB.shares());
(*it_t).transaction().modifySplit(splitA);
(*it_t).transaction().modifySplit(splitB);
} catch (const MyMoneyException &e) {
qDebug("Unable to update commodity to second splits currency in %s: '%s'", qPrintable((*it_t).transaction().id()), qPrintable(e.what()));
}
break;
default:
// TODO: use new logic by adjusting all splits by the price
// extracted from the selected split. Inform the user that
// this will happen and allow him to stop the processing (rc = false)
try {
QString msg;
if (firstTimeMultiCurrency) {
firstTimeMultiCurrency = false;
if (!isMultiSelection()) {
msg = i18n("This transaction has more than two splits and is originally based on a different currency (%1). Using this account to modify the transaction may result in rounding errors. Do you want to continue?", osec.name());
} else {
msg = i18n("At least one of the selected transactions has more than two splits and is originally based on a different currency (%1). Using this account to modify the transactions may result in rounding errors. Do you want to continue?", osec.name());
}
if (KMessageBox::warningContinueCancel(0, QString("<qt>%1</qt>").arg(msg)) == KMessageBox::Cancel) {
rc = false;
}
}
if (rc == true) {
MyMoneyMoney price;
if (!(*it_t).split().shares().isZero() && !(*it_t).split().value().isZero())
price = (*it_t).split().shares() / (*it_t).split().value();
QList<MyMoneySplit>::iterator it_s;
MyMoneySplit& mySplit = (*it_t).split();
for (it_s = (*it_t).transaction().splits().begin(); it_s != (*it_t).transaction().splits().end(); ++it_s) {
MyMoneySplit s = (*it_s);
if (s == mySplit) {
s.setValue(s.shares());
if (mySplit == m_split) {
m_split = s;
}
mySplit = s;
} else {
s.setValue((s.value() * price).convert(fract));
}
(*it_t).transaction().modifySplit(s);
}
}
} catch (const MyMoneyException &e) {
qDebug("Unable to update commodity of split currency in %s: '%s'", qPrintable((*it_t).transaction().id()), qPrintable(e.what()));
}
break;
}
// set the transaction's ommodity to this account's currency
(*it_t).transaction().setCommodity(m_account.currencyId());
// update our copy of the transaction that has the focus
if ((*it_t).transaction().id() == m_transaction.id()) {
m_transaction = (*it_t).transaction();
}
}
}
return rc;
}
void TransactionEditor::assignNextNumber()
{
if (canAssignNumber()) {
kMyMoneyLineEdit* number = dynamic_cast<kMyMoneyLineEdit*>(haveWidget("number"));
QString num = KMyMoneyUtils::nextCheckNumber(m_account);
bool showMessage = true;
int rc = KMessageBox::No;
QString schedInfo;
if (!m_scheduleInfo.isEmpty()) {
schedInfo = i18n("<center>Processing schedule for %1.</center>", m_scheduleInfo);
}
while (MyMoneyFile::instance()->checkNoUsed(m_account.id(), num)) {
if (showMessage) {
rc = KMessageBox::questionYesNo(m_regForm, QString("<qt>") + schedInfo + i18n("Check number <b>%1</b> has already been used in account <b>%2</b>."
"<center>Do you want to replace it with the next available number?</center>", num, m_account.name()) + QString("</qt>"), i18n("Duplicate number"));
showMessage = false;
}
if (rc == KMessageBox::Yes) {
num = KMyMoneyUtils::nextCheckNumber(m_account);
KMyMoneyUtils::updateLastNumberUsed(m_account, num);
m_account.setValue("lastNumberUsed", num);
number->loadText(num);
} else {
num = QString();
break;
}
}
number->setText(num);
}
}
bool TransactionEditor::canAssignNumber() const
{
kMyMoneyLineEdit* number = dynamic_cast<kMyMoneyLineEdit*>(haveWidget("number"));
return (number != 0);
}
void TransactionEditor::setupCategoryWidget(KMyMoneyCategory* category, const QList<MyMoneySplit>& splits, QString& categoryId, const char* splitEditSlot, bool /* allowObjectCreation */)
{
disconnect(category, SIGNAL(focusIn()), this, splitEditSlot);
#if 0
// FIXME must deal with the logic that suppressObjectCreation is
// automatically turned off when the createItem() signal is connected
if (allowObjectCreation)
category->setSuppressObjectCreation(false);
#endif
switch (splits.count()) {
case 0:
categoryId.clear();
if (!category->currentText().isEmpty()) {
// category->clearEditText(); // don't clear as could be from another widget - Bug 322768
// make sure, we don't see the selector
category->completion()->hide();
}
category->completion()->setSelected(QString());
break;
case 1:
categoryId = splits[0].accountId();
category->completion()->setSelected(categoryId);
category->slotItemSelected(categoryId);
break;
default:
categoryId.clear();
category->setSplitTransaction();
connect(category, SIGNAL(focusIn()), this, splitEditSlot);
#if 0
// FIXME must deal with the logic that suppressObjectCreation is
// automatically turned off when the createItem() signal is connected
if (allowObjectCreation)
category->setSuppressObjectCreation(true);
#endif
break;
}
}
bool TransactionEditor::enterTransactions(QString& newId, bool askForSchedule, bool suppressBalanceWarnings)
{
newId.clear();
MyMoneyFile* file = MyMoneyFile::instance();
// make sure to run through all stuff that is tied to 'focusout events'.
m_regForm->parentWidget()->setFocus();
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 10);
// we don't need to update our widgets anymore, so we just disconnect the signal
disconnect(file, SIGNAL(dataChanged()), this, SLOT(slotReloadEditWidgets()));
KMyMoneyRegister::SelectedTransactions::iterator it_t;
MyMoneyTransaction t;
bool newTransactionCreated = false;
// make sure, that only a single new transaction can be created.
// we need to update m_transactions to contain the new transaction
// which is then stored in the variable t when we leave the loop.
// m_transactions will be sent out in finishEdit() and forces
// the new transaction to be selected in the ledger view
// collect the transactions to be stored in the engine in a local
// list first, so that the user has a chance to interrupt the storage
// process
QList<MyMoneyTransaction> list;
bool storeTransactions = true;
// collect transactions
for (it_t = m_transactions.begin(); storeTransactions && !newTransactionCreated && it_t != m_transactions.end(); ++it_t) {
storeTransactions = createTransaction(t, (*it_t).transaction(), (*it_t).split());
// if the transaction was created successfully, append it to the list
if (storeTransactions)
list.append(t);
// if we created a new transaction keep that in mind
if (t.id().isEmpty())
newTransactionCreated = true;
}
// if not interrupted by user, continue to store them in the engine
if (storeTransactions) {
int i = 0;
emit statusMsg(i18n("Storing transactions"));
emit statusProgress(0, list.count());
MyMoneyFileTransaction ft;
try {
QList<MyMoneyTransaction>::iterator it_ts;
QMap<QString, bool> minBalanceEarly;
QMap<QString, bool> minBalanceAbsolute;
QMap<QString, bool> maxCreditEarly;
QMap<QString, bool> maxCreditAbsolute;
QMap<QString, bool> accountIds;
for (it_ts = list.begin(); it_ts != list.end(); ++it_ts) {
// if we have a categorization, make sure we remove
// the 'imported' flag automagically
if ((*it_ts).splitCount() > 1)
(*it_ts).setImported(false);
// create information about min and max balances
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = (*it_ts).splits().constBegin(); it_s != (*it_ts).splits().constEnd(); ++it_s) {
MyMoneyAccount acc = file->account((*it_s).accountId());
accountIds[acc.id()] = true;
MyMoneyMoney balance = file->balance(acc.id());
if (!acc.value("minBalanceEarly").isEmpty()) {
minBalanceEarly[acc.id()] = balance < MyMoneyMoney(acc.value("minBalanceEarly"));
}
if (!acc.value("minBalanceAbsolute").isEmpty()) {
minBalanceAbsolute[acc.id()] = balance < MyMoneyMoney(acc.value("minBalanceAbsolute"));
minBalanceEarly[acc.id()] = false;
}
if (!acc.value("maxCreditEarly").isEmpty()) {
maxCreditEarly[acc.id()] = balance < MyMoneyMoney(acc.value("maxCreditEarly"));
}
if (!acc.value("maxCreditAbsolute").isEmpty()) {
maxCreditAbsolute[acc.id()] = balance < MyMoneyMoney(acc.value("maxCreditAbsolute"));
maxCreditEarly[acc.id()] = false;
}
}
if ((*it_ts).id().isEmpty()) {
bool enter = true;
if (askForSchedule && (*it_ts).postDate() > QDate::currentDate()) {
KGuiItem enterButton(i18n("&Enter"),
QIcon::fromTheme(g_Icons[Icon::DialogOK]),
i18n("Accepts the entered data and stores it"),
i18n("Use this to enter the transaction into the ledger."));
KGuiItem scheduleButton(i18n("&Schedule"),
QIcon::fromTheme(g_Icons[Icon::AppointmentNew]),
i18n("Accepts the entered data and stores it as schedule"),
i18n("Use this to schedule the transaction for later entry into the ledger."));
enter = KMessageBox::questionYesNo(m_regForm, QString("<qt>%1</qt>").arg(i18n("The transaction you are about to enter has a post date in the future.<br/><br/>Do you want to enter it in the ledger or add it to the schedules?")), i18nc("Dialog caption for 'Enter or schedule' dialog", "Enter or schedule?"), enterButton, scheduleButton, "EnterOrScheduleTransactionInFuture") == KMessageBox::Yes;
}
if (enter) {
// add new transaction
file->addTransaction(*it_ts);
// pass the newly assigned id on to the caller
newId = (*it_ts).id();
// refresh account object for transactional changes
// refresh account and transaction object because they might have changed
m_account = file->account(m_account.id());
t = (*it_ts);
// if a new transaction has a valid number, keep it with the account
keepNewNumber((*it_ts));
} else {
// turn object creation on, so that moving the focus does
// not screw up the dialog that might be popping up
emit objectCreation(true);
- emit scheduleTransaction(*it_ts, MyMoneySchedule::OCCUR_ONCE);
+ emit scheduleTransaction(*it_ts, eMyMoney::Schedule::Occurrence::Once);
emit objectCreation(false);
newTransactionCreated = false;
}
// send out the post date of this transaction
emit lastPostDateUsed((*it_ts).postDate());
} else {
// modify existing transaction
// its number might have been edited
// bearing in mind it could contain alpha characters
keepNewNumber((*it_ts));
file->modifyTransaction(*it_ts);
}
}
emit statusProgress(i++, 0);
// update m_transactions to contain the newly created transaction so that
// it is selected as the current one
// we need to do that before we commit the transaction to the engine
// as we need it during the update of the views that is caused by committing already.
if (newTransactionCreated) {
m_transactions.clear();
MyMoneySplit s;
// a transaction w/o a single split should not exist and adding it
// should throw an exception in MyMoneyFile::addTransaction, but we
// remain on the save side of things to check for it
if (t.splitCount() > 0)
s = t.splits().front();
KMyMoneyRegister::SelectedTransaction st(t, s);
m_transactions.append(st);
}
// Save pricing information
QList<MyMoneySplit>::const_iterator it_t;
for (it_t = t.splits().constBegin(); it_t != t.splits().constEnd(); ++it_t) {
if (((*it_t).action() != "Buy") &&
((*it_t).action() != "Reinvest")) {
continue;
}
QString id = (*it_t).accountId();
MyMoneyAccount acc = file->account(id);
MyMoneySecurity sec = file->security(acc.currencyId());
MyMoneyPrice price(acc.currencyId(),
sec.tradingCurrency(),
t.postDate(),
(*it_t).price(), "Transaction");
file->addPrice(price);
break;
}
ft.commit();
// now analyze the balances and spit out warnings to the user
QMap<QString, bool>::const_iterator it_a;
if (!suppressBalanceWarnings) {
for (it_a = accountIds.constBegin(); it_a != accountIds.constEnd(); ++it_a) {
QString msg;
MyMoneyAccount acc = file->account(it_a.key());
MyMoneyMoney balance = file->balance(acc.id());
const MyMoneySecurity& sec = file->security(acc.currencyId());
QString key;
key = "minBalanceEarly";
if (!acc.value(key).isEmpty()) {
if (minBalanceEarly[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
msg = QString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the warning balance of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec)));
}
}
key = "minBalanceAbsolute";
if (!acc.value(key).isEmpty()) {
if (minBalanceAbsolute[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
msg = QString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the minimum balance of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec)));
}
}
key = "maxCreditEarly";
if (!acc.value(key).isEmpty()) {
if (maxCreditEarly[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
msg = QString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the maximum credit warning limit of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec)));
}
}
key = "maxCreditAbsolute";
if (!acc.value(key).isEmpty()) {
if (maxCreditAbsolute[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
msg = QString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the maximum credit limit of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec)));
}
}
if (!msg.isEmpty()) {
emit balanceWarning(m_regForm, acc, msg);
}
}
}
} catch (const MyMoneyException &e) {
qDebug("Unable to store transaction within engine: %s", qPrintable(e.what()));
newTransactionCreated = false;
}
emit statusProgress(-1, -1);
emit statusMsg(QString());
}
return storeTransactions;
}
void TransactionEditor::keepNewNumber(const MyMoneyTransaction& tr)
{
// verify that new number, possibly containing alpha, is valid
MyMoneyTransaction txn = tr;
MyMoneyFile* file = MyMoneyFile::instance();
if (!txn.splits().isEmpty()) {
QString number = txn.splits().first().number();
if (KMyMoneyUtils::numericPart(number) > 0) {
// numeric is valid
kMyMoneyLineEdit* numberEdit = dynamic_cast<kMyMoneyLineEdit*>(haveWidget("number"));
if (numberEdit) {
numberEdit->loadText(number);
MyMoneySplit split = txn.splits().first();
split.setNumber(number);
txn.modifySplit(split);
m_account.setValue("lastNumberUsed", number);
file->modifyAccount(m_account);
}
}
}
}
void TransactionEditor::resizeForm()
{
// force resizeing of the columns in the form
KMyMoneyTransactionForm::TransactionForm* form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(m_regForm);
if (form) {
QMetaObject::invokeMethod(form, "resize", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(int, ValueColumn1));
}
}
StdTransactionEditor::StdTransactionEditor() :
m_inUpdateVat(false)
{
}
StdTransactionEditor::StdTransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::Transaction* item, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate) :
TransactionEditor(regForm, item, list, lastPostDate),
m_inUpdateVat(false)
{
}
StdTransactionEditor::~StdTransactionEditor()
{
}
void StdTransactionEditor::createEditWidgets()
{
// we only create the account widget in case it is needed
// to avoid confusion in the tab order later on.
if (m_item->showRowInForm(0)) {
KMyMoneyCategory* account = new KMyMoneyCategory;
account->setPlaceholderText(i18n("Account"));
account->setObjectName(QLatin1String("Account"));
m_editWidgets["account"] = account;
connect(account, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateButtonState()));
connect(account, SIGNAL(itemSelected(QString)), this, SLOT(slotUpdateAccount(QString)));
}
KMyMoneyPayeeCombo* payee = new KMyMoneyPayeeCombo;
payee->setPlaceholderText(i18n("Payer/Receiver"));
payee->setObjectName(QLatin1String("Payee"));
m_editWidgets["payee"] = payee;
connect(payee, SIGNAL(createItem(QString,QString&)), this, SIGNAL(createPayee(QString,QString&)));
connect(payee, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool)));
connect(payee, SIGNAL(itemSelected(QString)), this, SLOT(slotUpdatePayee(QString)));
connect(payee, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateButtonState()));
KMyMoneyCategory* category = new KMyMoneyCategory(0, true);
category->setPlaceholderText(i18n("Category/Account"));
category->setObjectName(QLatin1String("Category/Account"));
m_editWidgets["category"] = category;
connect(category, SIGNAL(itemSelected(QString)), this, SLOT(slotUpdateCategory(QString)));
connect(category, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateButtonState()));
connect(category, SIGNAL(createItem(QString,QString&)), this, SLOT(slotCreateCategory(QString,QString&)));
connect(category, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool)));
connect(category->splitButton(), SIGNAL(clicked()), this, SLOT(slotEditSplits()));
// initially disable the split button since we don't have an account set
if (category->splitButton())
category->splitButton()->setDisabled(m_account.id().isEmpty());
KTagContainer* tag = new KTagContainer;
tag->tagCombo()->setPlaceholderText(i18n("Tag"));
tag->tagCombo()->setObjectName(QLatin1String("Tag"));
m_editWidgets["tag"] = tag;
connect(tag->tagCombo(), SIGNAL(createItem(QString,QString&)), this, SIGNAL(createTag(QString,QString&)));
connect(tag->tagCombo(), SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool)));
KTextEdit* memo = new KTextEdit;
memo->setObjectName(QLatin1String("Memo"));
memo->setTabChangesFocus(true);
connect(memo, SIGNAL(textChanged()), this, SLOT(slotUpdateMemoState()));
connect(memo, SIGNAL(textChanged()), this, SLOT(slotUpdateButtonState()));
m_editWidgets["memo"] = memo;
m_memoText.clear();
m_memoChanged = false;
bool showNumberField = true;
switch (m_account.accountType()) {
- case MyMoneyAccount::Savings:
- case MyMoneyAccount::Cash:
- case MyMoneyAccount::Loan:
- case MyMoneyAccount::AssetLoan:
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Equity:
+ case eMyMoney::Account::Savings:
+ case eMyMoney::Account::Cash:
+ case eMyMoney::Account::Loan:
+ case eMyMoney::Account::AssetLoan:
+ case eMyMoney::Account::Asset:
+ case eMyMoney::Account::Liability:
+ case eMyMoney::Account::Equity:
showNumberField = KMyMoneyGlobalSettings::alwaysShowNrField();
break;
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
+ case eMyMoney::Account::Income:
+ case eMyMoney::Account::Expense:
showNumberField = false;
break;
default:
break;
}
if (showNumberField) {
kMyMoneyLineEdit* number = new kMyMoneyLineEdit;
number->setPlaceholderText(i18n("Number"));
number->setObjectName(QLatin1String("Number"));
m_editWidgets["number"] = number;
connect(number, SIGNAL(lineChanged(QString)), this, SLOT(slotNumberChanged(QString)));
// number->installEventFilter(this);
}
kMyMoneyDateInput* postDate = new kMyMoneyDateInput;
m_editWidgets["postdate"] = postDate;
postDate->setObjectName(QLatin1String("PostDate"));
connect(postDate, SIGNAL(dateChanged(QDate)), this, SLOT(slotUpdateButtonState()));
postDate->setDate(QDate());
kMyMoneyEdit* value = new kMyMoneyEdit;
m_editWidgets["amount"] = value;
value->setObjectName(QLatin1String("Amount"));
value->setResetButtonVisible(false);
connect(value, SIGNAL(valueChanged(QString)), this, SLOT(slotUpdateAmount(QString)));
connect(value, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButtonState()));
value = new kMyMoneyEdit;
m_editWidgets["payment"] = value;
value->setObjectName(QLatin1String("Payment"));
value->setResetButtonVisible(false);
connect(value, SIGNAL(valueChanged(QString)), this, SLOT(slotUpdatePayment(QString)));
connect(value, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButtonState()));
value = new kMyMoneyEdit;
m_editWidgets["deposit"] = value;
value->setObjectName(QLatin1String("Deposit"));
value->setResetButtonVisible(false);
connect(value, SIGNAL(valueChanged(QString)), this, SLOT(slotUpdateDeposit(QString)));
connect(value, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButtonState()));
KMyMoneyCashFlowCombo* cashflow = new KMyMoneyCashFlowCombo(0, m_account.accountGroup());
m_editWidgets["cashflow"] = cashflow;
cashflow->setObjectName(QLatin1String("Cashflow"));
connect(cashflow, SIGNAL(directionSelected(KMyMoneyRegister::CashFlowDirection)), this, SLOT(slotUpdateCashFlow(KMyMoneyRegister::CashFlowDirection)));
KMyMoneyReconcileCombo* reconcile = new KMyMoneyReconcileCombo;
m_editWidgets["status"] = reconcile;
reconcile->setObjectName(QLatin1String("Reconcile"));
KMyMoneyRegister::QWidgetContainer::iterator it_w;
for (it_w = m_editWidgets.begin(); it_w != m_editWidgets.end(); ++it_w) {
(*it_w)->installEventFilter(this);
}
// if we don't have more than 1 selected transaction, we don't need
// the "don't change" item in some of the combo widgets
if (!isMultiSelection()) {
reconcile->removeDontCare();
cashflow->removeDontCare();
}
QLabel* label;
m_editWidgets["account-label"] = label = new QLabel(i18n("Account"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["category-label"] = label = new QLabel(i18n("Category"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["tag-label"] = label = new QLabel(i18n("Tags"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["memo-label"] = label = new QLabel(i18n("Memo"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["number-label"] = label = new QLabel(i18n("Number"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["date-label"] = label = new QLabel(i18n("Date"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["amount-label"] = label = new QLabel(i18n("Amount"));
label->setAlignment(Qt::AlignVCenter);
m_editWidgets["status-label"] = label = new QLabel(i18n("Status"));
label->setAlignment(Qt::AlignVCenter);
// create a copy of tabbar above the form (if we are created for a form)
KMyMoneyTransactionForm::TransactionForm* form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(m_regForm);
if (form) {
KMyMoneyTransactionForm::TabBar* tabbar = new KMyMoneyTransactionForm::TabBar;
m_editWidgets["tabbar"] = tabbar;
tabbar->setObjectName(QLatin1String("TabBar"));
tabbar->copyTabs(form->tabBar());
connect(tabbar, SIGNAL(tabCurrentChanged(int)), this, SLOT(slotUpdateAction(int)));
connect(tabbar, SIGNAL(tabCurrentChanged(int)), this, SIGNAL(operationTypeChanged(int)));
}
setupPrecision();
}
void StdTransactionEditor::setupCategoryWidget(QString& categoryId)
{
TransactionEditor::setupCategoryWidget(dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]), m_splits, categoryId, SLOT(slotEditSplits()));
if (m_splits.count() == 1)
m_shares = m_splits[0].shares();
}
bool StdTransactionEditor::isTransfer(const QString& accId1, const QString& accId2) const
{
if (accId1.isEmpty() || accId2.isEmpty())
return false;
return MyMoneyFile::instance()->account(accId1).isIncomeExpense() == MyMoneyFile::instance()->account(accId2).isIncomeExpense();
}
void StdTransactionEditor::loadEditWidgets(KMyMoneyRegister::Action action)
{
// don't kick off VAT processing from here
m_inUpdateVat = true;
QMap<QString, QWidget*>::const_iterator it_w;
QWidget* w;
AccountSet aSet;
// load the account widget
KMyMoneyCategory* account = dynamic_cast<KMyMoneyCategory*>(haveWidget("account"));
if (account) {
- aSet.addAccountGroup(MyMoneyAccount::Asset);
- aSet.addAccountGroup(MyMoneyAccount::Liability);
- aSet.removeAccountType(MyMoneyAccount::AssetLoan);
- aSet.removeAccountType(MyMoneyAccount::CertificateDep);
- aSet.removeAccountType(MyMoneyAccount::Investment);
- aSet.removeAccountType(MyMoneyAccount::Stock);
- aSet.removeAccountType(MyMoneyAccount::MoneyMarket);
- aSet.removeAccountType(MyMoneyAccount::Loan);
+ aSet.addAccountGroup(eMyMoney::Account::Asset);
+ aSet.addAccountGroup(eMyMoney::Account::Liability);
+ aSet.removeAccountType(eMyMoney::Account::AssetLoan);
+ aSet.removeAccountType(eMyMoney::Account::CertificateDep);
+ aSet.removeAccountType(eMyMoney::Account::Investment);
+ aSet.removeAccountType(eMyMoney::Account::Stock);
+ aSet.removeAccountType(eMyMoney::Account::MoneyMarket);
+ aSet.removeAccountType(eMyMoney::Account::Loan);
aSet.load(account->selector());
account->completion()->setSelected(m_account.id());
account->slotItemSelected(m_account.id());
}
// load the payee widget
KMyMoneyPayeeCombo* payee = dynamic_cast<KMyMoneyPayeeCombo*>(m_editWidgets["payee"]);
payee->loadPayees(MyMoneyFile::instance()->payeeList());
// load the category widget
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
disconnect(category, SIGNAL(focusIn()), this, SLOT(slotEditSplits()));
// load the tag widget
//KMyMoneyTagCombo* tag = dynamic_cast<KMyMoneyTagCombo*>(m_editWidgets["tag"]);
KTagContainer* tag = dynamic_cast<KTagContainer*>(m_editWidgets["tag"]);
tag->loadTags(MyMoneyFile::instance()->tagList());
// check if the current transaction has a reference to an equity account
bool haveEquityAccount = false;
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = m_transaction.splits().constBegin(); !haveEquityAccount && it_s != m_transaction.splits().constEnd(); ++it_s) {
MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
- if (acc.accountType() == MyMoneyAccount::Equity)
+ if (acc.accountType() == eMyMoney::Account::Equity)
haveEquityAccount = true;
}
aSet.clear();
- aSet.addAccountGroup(MyMoneyAccount::Asset);
- aSet.addAccountGroup(MyMoneyAccount::Liability);
- aSet.addAccountGroup(MyMoneyAccount::Income);
- aSet.addAccountGroup(MyMoneyAccount::Expense);
+ aSet.addAccountGroup(eMyMoney::Account::Asset);
+ aSet.addAccountGroup(eMyMoney::Account::Liability);
+ aSet.addAccountGroup(eMyMoney::Account::Income);
+ aSet.addAccountGroup(eMyMoney::Account::Expense);
if (KMyMoneyGlobalSettings::expertMode() || haveEquityAccount)
- aSet.addAccountGroup(MyMoneyAccount::Equity);
+ aSet.addAccountGroup(eMyMoney::Account::Equity);
- aSet.removeAccountType(MyMoneyAccount::CertificateDep);
- aSet.removeAccountType(MyMoneyAccount::Investment);
- aSet.removeAccountType(MyMoneyAccount::Stock);
- aSet.removeAccountType(MyMoneyAccount::MoneyMarket);
+ aSet.removeAccountType(eMyMoney::Account::CertificateDep);
+ aSet.removeAccountType(eMyMoney::Account::Investment);
+ aSet.removeAccountType(eMyMoney::Account::Stock);
+ aSet.removeAccountType(eMyMoney::Account::MoneyMarket);
aSet.load(category->selector());
// if an account is specified then remove it from the widget so that the user
// cannot create a transfer with from and to account being the same account
if (!m_account.id().isEmpty())
category->selector()->removeItem(m_account.id());
// also show memo text if isMultiSelection()
dynamic_cast<KTextEdit*>(m_editWidgets["memo"])->setText(m_split.memo());
// need to know if it changed
m_memoText = m_split.memo();
m_memoChanged = false;
if (!isMultiSelection()) {
if (m_transaction.postDate().isValid())
dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->setDate(m_transaction.postDate());
else if (m_lastPostDate.isValid())
dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->setDate(m_lastPostDate);
else
dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->setDate(QDate::currentDate());
if ((w = haveWidget("number")) != 0) {
dynamic_cast<kMyMoneyLineEdit*>(w)->loadText(m_split.number());
if (m_transaction.id().isEmpty() // new transaction
&& dynamic_cast<kMyMoneyLineEdit*>(w)->text().isEmpty() // no number filled in
- && m_account.accountType() == MyMoneyAccount::Checkings // checkings account
+ && m_account.accountType() == eMyMoney::Account::Checkings // checkings account
&& KMyMoneyGlobalSettings::autoIncCheckNumber() // and auto inc number turned on?
&& action != KMyMoneyRegister::ActionDeposit // only transfers or withdrawals
- && m_paymentMethod == MyMoneySchedule::STYPE_WRITECHEQUE) {// only for STYPE_WRITECHEQUE
+ && m_paymentMethod == eMyMoney::Schedule::PaymentType::WriteChecque) {// only for WriteChecque
assignNextNumber();
}
}
dynamic_cast<KMyMoneyReconcileCombo*>(m_editWidgets["status"])->setState(m_split.reconcileFlag());
QString payeeId = m_split.payeeId();
if (!payeeId.isEmpty()) {
payee->setSelectedItem(payeeId);
}
QList<QString> t = m_split.tagIdList();
if (!t.isEmpty()) {
for (int i = 0; i < t.size(); i++)
tag->addTagWidget(t[i]);
}
m_splits.clear();
if (m_transaction.splitCount() < 2) {
category->completion()->setSelected(QString());
} else {
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = m_transaction.splits().constBegin(); it_s != m_transaction.splits().constEnd(); ++it_s) {
if ((*it_s) == m_split)
continue;
m_splits.append(*it_s);
}
}
QString categoryId;
setupCategoryWidget(categoryId);
if ((w = haveWidget("cashflow")) != 0) {
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(w);
cashflow->setDirection(!m_split.value().isPositive() ? KMyMoneyRegister::Payment : KMyMoneyRegister::Deposit); // include isZero case
}
if ((w = haveWidget("category-label")) != 0) {
QLabel *categoryLabel = dynamic_cast<QLabel*>(w);
if (isTransfer(m_split.accountId(), categoryId)) {
if (m_split.value().isPositive())
categoryLabel->setText(i18n("Transfer from"));
else
categoryLabel->setText(i18n("Transfer to"));
}
}
MyMoneyMoney value = m_split.shares();
if (haveWidget("deposit")) {
if (m_split.shares().isNegative()) {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->loadText("");
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->setValue(value.abs());
} else {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->setValue(value.abs());
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->loadText("");
}
}
if ((w = haveWidget("amount")) != 0) {
dynamic_cast<kMyMoneyEdit*>(w)->setValue(value.abs());
}
slotUpdateCategory(categoryId);
// try to preset for specific action if a new transaction is being started
if (m_transaction.id().isEmpty()) {
if ((w = haveWidget("category-label")) != 0) {
TabBar* tabbar = dynamic_cast<TabBar*>(haveWidget("tabbar"));
if (action == KMyMoneyRegister::ActionNone) {
if (tabbar) {
action = static_cast<KMyMoneyRegister::Action>(tabbar->currentIndex());
}
}
if (action != KMyMoneyRegister::ActionNone) {
QLabel *categoryLabel = dynamic_cast<QLabel*>(w);
if (action == KMyMoneyRegister::ActionTransfer) {
if (m_split.value().isPositive())
categoryLabel->setText(i18n("Transfer from"));
else
categoryLabel->setText(i18n("Transfer to"));
}
if ((w = haveWidget("cashflow")) != 0) {
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(w);
if (action == KMyMoneyRegister::ActionDeposit || (action == KMyMoneyRegister::ActionTransfer && m_split.value().isPositive()))
cashflow->setDirection(KMyMoneyRegister::Deposit);
else
cashflow->setDirection(KMyMoneyRegister::Payment);
}
if (tabbar) {
tabbar->setCurrentIndex(action);
}
}
}
} else {
TabBar* tabbar = dynamic_cast<TabBar*>(haveWidget("tabbar"));
if (tabbar) {
if (!isTransfer(m_split.accountId(), categoryId)) {
tabbar->setCurrentIndex(m_split.value().isNegative() ? KMyMoneyRegister::ActionWithdrawal : KMyMoneyRegister::ActionDeposit);
} else {
tabbar->setCurrentIndex(KMyMoneyRegister::ActionTransfer);
}
}
}
} else { // isMultiSelection()
dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->loadDate(QDate());
dynamic_cast<KMyMoneyReconcileCombo*>(m_editWidgets["status"])->setState(MyMoneySplit::Unknown);
if (haveWidget("deposit")) {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->loadText("");
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->setAllowEmpty();
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->loadText("");
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->setAllowEmpty();
}
if ((w = haveWidget("amount")) != 0) {
dynamic_cast<kMyMoneyEdit*>(w)->loadText("");
dynamic_cast<kMyMoneyEdit*>(w)->setAllowEmpty();
}
slotUpdateAction(action);
if ((w = haveWidget("tabbar")) != 0) {
w->setEnabled(false);
}
category->completion()->setSelected(QString());
}
// allow kick off VAT processing again
m_inUpdateVat = false;
}
QWidget* StdTransactionEditor::firstWidget() const
{
QWidget* w = 0;
if (m_initialAction != KMyMoneyRegister::ActionNone) {
w = haveWidget("payee");
}
return w;
}
void StdTransactionEditor::slotReloadEditWidgets()
{
// reload category widget
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
QString categoryId = category->selectedItem();
AccountSet aSet;
- aSet.addAccountGroup(MyMoneyAccount::Asset);
- aSet.addAccountGroup(MyMoneyAccount::Liability);
- aSet.addAccountGroup(MyMoneyAccount::Income);
- aSet.addAccountGroup(MyMoneyAccount::Expense);
+ aSet.addAccountGroup(eMyMoney::Account::Asset);
+ aSet.addAccountGroup(eMyMoney::Account::Liability);
+ aSet.addAccountGroup(eMyMoney::Account::Income);
+ aSet.addAccountGroup(eMyMoney::Account::Expense);
if (KMyMoneyGlobalSettings::expertMode())
- aSet.addAccountGroup(MyMoneyAccount::Equity);
+ aSet.addAccountGroup(eMyMoney::Account::Equity);
aSet.load(category->selector());
// if an account is specified then remove it from the widget so that the user
// cannot create a transfer with from and to account being the same account
if (!m_account.id().isEmpty())
category->selector()->removeItem(m_account.id());
if (!categoryId.isEmpty())
category->setSelectedItem(categoryId);
// reload payee widget
KMyMoneyPayeeCombo* payee = dynamic_cast<KMyMoneyPayeeCombo*>(m_editWidgets["payee"]);
QString payeeId = payee->selectedItem();
payee->loadPayees(MyMoneyFile::instance()->payeeList());
if (!payeeId.isEmpty()) {
payee->setSelectedItem(payeeId);
}
// reload tag widget
KTagContainer* tag = dynamic_cast<KTagContainer*>(m_editWidgets["tag"]);
QString tagId = tag->tagCombo()->selectedItem();
tag->loadTags(MyMoneyFile::instance()->tagList());
if (!tagId.isEmpty()) {
tag->RemoveAllTagWidgets();
tag->addTagWidget(tagId);
}
}
void StdTransactionEditor::slotUpdatePayee(const QString& payeeId)
{
// we have a new payee assigned to this transaction.
// in case there is no category assigned, no value entered and no
// memo available, we search for the last transaction of this payee
// in the account.
if (m_transaction.id().isEmpty()
&& m_splits.count() == 0
&& KMyMoneyGlobalSettings::autoFillTransaction() != 0) {
// check if category is empty
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
QStringList list;
category->selectedItems(list);
if (!list.isEmpty())
return;
// check if memo is empty
KTextEdit* memo = dynamic_cast<KTextEdit*>(m_editWidgets["memo"]);
if (memo && !memo->toPlainText().isEmpty())
return;
// check if all value fields are empty
kMyMoneyEdit* amount;
QStringList fields;
fields << "amount" << "payment" << "deposit";
QStringList::const_iterator it_f;
for (it_f = fields.constBegin(); it_f != fields.constEnd(); ++it_f) {
amount = dynamic_cast<kMyMoneyEdit*>(haveWidget(*it_f));
if (amount && !amount->value().isZero())
return;
}
#if 0
// Tony mentioned, that autofill does not work when he changed the date. Well,
// that certainly makes sense when you enter transactions in register mode as
// opposed to form mode, because the date field is located prior to the date
// field in the tab order of the widgets and the user might have already
// changed it.
//
// So I commented out the code that checks the date but left it in for reference.
// (ipwizard, 2008-04-07)
// check if date has been altered by user
kMyMoneyDateInput* postDate = dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"]);
if ((m_lastPostDate.isValid() && (postDate->date() != m_lastPostDate))
|| (!m_lastPostDate.isValid() && (postDate->date() != QDate::currentDate())))
return;
#endif
// if we got here, we have to autofill
autoFill(payeeId);
}
// If payee has associated default account (category), set that now.
const MyMoneyPayee& payeeObj = MyMoneyFile::instance()->payee(payeeId);
if (payeeObj.defaultAccountEnabled()) {
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
category->slotItemSelected(payeeObj.defaultAccountId());
}
}
MyMoneyMoney StdTransactionEditor::shares(const MyMoneyTransaction& t) const
{
MyMoneyMoney result;
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
if ((*it_s).accountId() == m_account.id()) {
result += (*it_s).shares();
}
}
return result;
}
struct uniqTransaction {
const MyMoneyTransaction* t;
int cnt;
};
void StdTransactionEditor::autoFill(const QString& payeeId)
{
QList<QPair<MyMoneyTransaction, MyMoneySplit> > list;
MyMoneyTransactionFilter filter(m_account.id());
filter.addPayee(payeeId);
MyMoneyFile::instance()->transactionList(list, filter);
if (!list.empty()) {
// ok, we found at least one previous transaction. now we clear out
// what we have collected so far and add those splits from
// the previous transaction.
QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it_t;
QMap<QString, struct uniqTransaction> uniqList;
// collect the transactions and see if we have any duplicates
for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) {
QString key = (*it_t).first.accountSignature();
int cnt = 0;
QMap<QString, struct uniqTransaction>::iterator it_u;
do {
QString ukey = QString("%1-%2").arg(key).arg(cnt);
it_u = uniqList.find(ukey);
if (it_u == uniqList.end()) {
uniqList[ukey].t = &((*it_t).first);
uniqList[ukey].cnt = 1;
} else if (KMyMoneyGlobalSettings::autoFillTransaction() == 1) {
// we already have a transaction with this signature. we must
// now check, if we should really treat it as a duplicate according
// to the value comparison delta.
MyMoneyMoney s1 = shares(*((*it_u).t));
MyMoneyMoney s2 = shares((*it_t).first);
if (s2.abs() > s1.abs()) {
MyMoneyMoney t(s1);
s1 = s2;
s2 = t;
}
MyMoneyMoney diff;
if (s2.isZero())
diff = s1.abs();
else
diff = ((s1 - s2) / s2).convert(10000);
if (diff.isPositive() && diff <= MyMoneyMoney(KMyMoneyGlobalSettings::autoFillDifference(), 100)) {
uniqList[ukey].t = &((*it_t).first);
break; // end while loop
}
} else if (KMyMoneyGlobalSettings::autoFillTransaction() == 2) {
(*it_u).cnt++;
break; // end while loop
}
++cnt;
} while (it_u != uniqList.end());
}
MyMoneyTransaction t;
if (KMyMoneyGlobalSettings::autoFillTransaction() != 2) {
#if 0
// I removed this code to allow cancellation of an autofill if
// it does not match even if there is only a single matching
// transaction for the payee in question. In case, we want to revert
// to the old behavior, don't forget to uncomment the closing
// brace further down in the code as well. (ipwizard 2009-01-16)
if (uniqList.count() == 1) {
t = list.last().first;
} else {
#endif
QPointer<KSelectTransactionsDlg> dlg = new KSelectTransactionsDlg(m_account, m_regForm);
dlg->setWindowTitle(i18n("Select autofill transaction"));
QMap<QString, struct uniqTransaction>::const_iterator it_u;
for (it_u = uniqList.constBegin(); it_u != uniqList.constEnd(); ++it_u) {
dlg->addTransaction(*(*it_u).t);
}
// setup sort order
dlg->m_register->setSortOrder("1,-9,-4");
// sort the transactions according to the sort setting
dlg->m_register->sortItems();
// and select the last item
if (dlg->m_register->lastItem())
dlg->m_register->selectItem(dlg->m_register->lastItem());
if (dlg->exec() == QDialog::Accepted) {
t = dlg->transaction();
}
#if 0
}
#endif
} else {
int maxCnt = 0;
QMap<QString, struct uniqTransaction>::const_iterator it_u;
for (it_u = uniqList.constBegin(); it_u != uniqList.constEnd(); ++it_u) {
if ((*it_u).cnt > maxCnt) {
t = *(*it_u).t;
maxCnt = (*it_u).cnt;
}
}
}
if (t != MyMoneyTransaction()) {
m_transaction.removeSplits();
m_split = MyMoneySplit();
MyMoneySplit otherSplit;
QList<MyMoneySplit>::ConstIterator it;
for (it = t.splits().constBegin(); it != t.splits().constEnd(); ++it) {
MyMoneySplit s(*it);
s.setReconcileFlag(MyMoneySplit::NotReconciled);
s.setReconcileDate(QDate());
s.clearId();
s.setBankID(QString());
// older versions of KMyMoney used to set the action
// we don't need this anymore
if (s.action() != MyMoneySplit::ActionAmortization
&& s.action() != MyMoneySplit::ActionInterest) {
s.setAction(QString());
}
// FIXME update check number. The old comment contained
//
// <quote>
// If a check number is already specified by the user it is
// used. If the input field is empty and the previous transaction
// contains a checknumber, the next usable check number will be assigned
// to the transaction.
// </quote>
kMyMoneyLineEdit* editNr = dynamic_cast<kMyMoneyLineEdit*>(haveWidget("number"));
if (editNr && !editNr->text().isEmpty()) {
s.setNumber(editNr->text());
} else if (!s.number().isEmpty()) {
s.setNumber(KMyMoneyUtils::nextCheckNumber(m_account));
}
// if the memos should not be used with autofill or
// if the transaction has exactly two splits, remove
// the memo text of the split that does not reference
// the current account. This allows the user to change
// the autofilled memo text which will then also be used
// in this split. See createTransaction() for this logic.
if ((s.accountId() != m_account.id() && t.splitCount() == 2) || !KMyMoneyGlobalSettings::autoFillUseMemos())
s.setMemo(QString());
m_transaction.addSplit(s);
if (s.accountId() == m_account.id() && m_split == MyMoneySplit()) {
m_split = s;
} else {
otherSplit = s;
}
}
// make sure to extract the right action
KMyMoneyRegister::Action action;
action = m_split.shares().isNegative() ? KMyMoneyRegister::ActionWithdrawal : KMyMoneyRegister::ActionDeposit;
if (m_transaction.splitCount() == 2) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(otherSplit.accountId());
if (acc.isAssetLiability())
action = KMyMoneyRegister::ActionTransfer;
}
// now setup the widgets with the new data but keep the date
QDate date = dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->date();
loadEditWidgets(action);
dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->setDate(date);
}
}
// focus jumps into the category field
QWidget* w;
if ((w = haveWidget("payee")) != 0) {
w->setFocus();
}
}
void StdTransactionEditor::slotUpdateAction(int action)
{
TabBar* tabbar = dynamic_cast<TabBar*>(haveWidget("tabbar"));
if (tabbar) {
QLabel* categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(m_editWidgets["cashflow"]);
switch (action) {
case KMyMoneyRegister::ActionDeposit:
categoryLabel->setText(i18n("Category"));
cashflow->setDirection(KMyMoneyRegister::Deposit);
break;
case KMyMoneyRegister::ActionTransfer:
if (m_split.shares().isNegative()) {
cashflow->setDirection(KMyMoneyRegister::Payment);
categoryLabel->setText(i18n("Transfer to"));
} else {
cashflow->setDirection(KMyMoneyRegister::Deposit);
categoryLabel->setText(i18n("Transfer from"));
}
tabbar->setCurrentIndex(KMyMoneyRegister::ActionTransfer);
slotUpdateCashFlow(cashflow->direction());
break;
case KMyMoneyRegister::ActionWithdrawal:
categoryLabel->setText(i18n("Category"));
cashflow->setDirection(KMyMoneyRegister::Payment);
break;
}
resizeForm();
}
}
void StdTransactionEditor::slotUpdateCashFlow(KMyMoneyRegister::CashFlowDirection dir)
{
QLabel* categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"));
cashflow->setDirection(dir);
// qDebug("Update cashflow to %d", dir);
if (categoryLabel) {
TabBar* tabbar = dynamic_cast<TabBar*>(haveWidget("tabbar"));
if (!tabbar) return; // no transaction form
if (categoryLabel->text() != i18n("Category")) {
tabbar->setCurrentIndex(KMyMoneyRegister::ActionTransfer);
if (dir == KMyMoneyRegister::Deposit) {
categoryLabel->setText(i18n("Transfer from"));
} else {
categoryLabel->setText(i18n("Transfer to"));
}
resizeForm();
} else {
if (dir == KMyMoneyRegister::Deposit)
tabbar->setCurrentIndex(KMyMoneyRegister::ActionDeposit);
else
tabbar->setCurrentIndex(KMyMoneyRegister::ActionWithdrawal);
}
}
}
void StdTransactionEditor::slotUpdateCategory(const QString& id)
{
QLabel *categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
// qDebug("Update category to %s", qPrintable(id));
if (categoryLabel) {
TabBar* tabbar = dynamic_cast<TabBar*>(haveWidget("tabbar"));
kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(m_editWidgets["amount"]);
MyMoneyMoney val = amount->value();
if (categoryLabel->text() == i18n("Transfer from")) {
val = -val;
} else {
val = val.abs();
}
if (tabbar) {
tabbar->setTabEnabled(KMyMoneyRegister::ActionTransfer, true);
tabbar->setTabEnabled(KMyMoneyRegister::ActionDeposit, true);
tabbar->setTabEnabled(KMyMoneyRegister::ActionWithdrawal, true);
}
bool disableTransferTab = false;
if (!id.isEmpty()) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(id);
if (acc.isAssetLiability()
- || acc.accountGroup() == MyMoneyAccount::Equity) {
+ || acc.accountGroup() == eMyMoney::Account::Equity) {
if (tabbar) {
tabbar->setCurrentIndex(KMyMoneyRegister::ActionTransfer);
tabbar->setTabEnabled(KMyMoneyRegister::ActionDeposit, false);
tabbar->setTabEnabled(KMyMoneyRegister::ActionWithdrawal, false);
}
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(m_editWidgets["cashflow"]);
if (val.isZero()) {
if (cashflow && (cashflow->direction() == KMyMoneyRegister::Deposit)) {
categoryLabel->setText(i18n("Transfer from"));
} else {
categoryLabel->setText(i18n("Transfer to"));
}
} else if (val.isNegative()) {
categoryLabel->setText(i18n("Transfer from"));
cashflow->setDirection(KMyMoneyRegister::Deposit);
} else
categoryLabel->setText(i18n("Transfer to"));
} else {
disableTransferTab = true;
categoryLabel->setText(i18n("Category"));
}
updateAmount(val);
} else { //id.isEmpty()
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
disableTransferTab = !category->currentText().isEmpty();
categoryLabel->setText(i18n("Category"));
}
if (tabbar) {
if (disableTransferTab) {
// set the proper tab before disabling the currently active tab
if (tabbar->currentIndex() == KMyMoneyRegister::ActionTransfer) {
tabbar->setCurrentIndex(val.isPositive() ? KMyMoneyRegister::ActionWithdrawal : KMyMoneyRegister::ActionDeposit);
}
tabbar->setTabEnabled(KMyMoneyRegister::ActionTransfer, false);
}
tabbar->update();
}
resizeForm();
}
updateVAT(false);
}
void StdTransactionEditor::slotUpdatePayment(const QString& txt)
{
MyMoneyMoney val(txt);
if (val.isNegative()) {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->setValue(val.abs());
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->clearText();
} else {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->clearText();
}
updateVAT();
}
void StdTransactionEditor::slotUpdateDeposit(const QString& txt)
{
MyMoneyMoney val(txt);
if (val.isNegative()) {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->setValue(val.abs());
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->clearText();
} else {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->clearText();
}
updateVAT();
}
void StdTransactionEditor::slotUpdateAmount(const QString& txt)
{
// qDebug("Update amount to %s", qPrintable(txt));
MyMoneyMoney val(txt);
updateAmount(val);
updateVAT(true);
}
void StdTransactionEditor::updateAmount(const MyMoneyMoney& val)
{
// we don't do anything if we have multiple transactions selected
if (isMultiSelection())
return;
QLabel *categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label"));
if (categoryLabel) {
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(m_editWidgets["cashflow"]);
if (!val.isPositive()) { // fixes BUG321317
if (categoryLabel->text() != i18n("Category")) {
if (cashflow->direction() == KMyMoneyRegister::Payment) {
categoryLabel->setText(i18n("Transfer to"));
}
} else {
slotUpdateCashFlow(cashflow->direction());
}
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["amount"])->setValue(val.abs());
} else {
if (categoryLabel->text() != i18n("Category")) {
if (cashflow->direction() == KMyMoneyRegister::Payment) {
categoryLabel->setText(i18n("Transfer to"));
} else {
categoryLabel->setText(i18n("Transfer from"));
cashflow->setDirection(KMyMoneyRegister::Deposit); // editing with +ve shows 'from' not 'pay to'
}
}
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["amount"])->setValue(val.abs());
}
}
}
void StdTransactionEditor::updateVAT(bool amountChanged)
{
// make sure that we don't do this recursively
if (m_inUpdateVat)
return;
// we don't do anything if we have multiple transactions selected
if (isMultiSelection())
return;
// if auto vat assignment for this account is turned off
// we don't care about taxes
if (m_account.value("NoVat") == "Yes")
return;
// more splits than category and tax are not supported
if (m_splits.count() > 2)
return;
// in order to do anything, we need an amount
MyMoneyMoney amount, newAmount;
bool amountOk;
amount = amountFromWidget(&amountOk);
if (!amountOk)
return;
// If the transaction has a tax and a category split, remove the tax split
if (m_splits.count() == 2) {
newAmount = removeVatSplit();
if (m_splits.count() == 2) // not removed?
return;
} else {
// otherwise, we need a category
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
if (category->selectedItem().isEmpty())
return;
// if no VAT account is associated with this category/account, then we bail out
MyMoneyAccount cat = MyMoneyFile::instance()->account(category->selectedItem());
if (cat.value("VatAccount").isEmpty())
return;
newAmount = amount;
}
// seems we have everything we need
if (amountChanged)
newAmount = amount;
MyMoneyTransaction transaction;
if (createTransaction(transaction, m_transaction, m_split)) {
if (addVatSplit(transaction, newAmount)) {
m_transaction = transaction;
if (!m_transaction.splits().isEmpty())
m_split = m_transaction.splits().front();
loadEditWidgets();
// if we made this a split transaction, then move the
// focus to the memo field
if (qApp->focusWidget() == haveWidget("category")) {
QWidget* w = haveWidget("memo");
if (w)
w->setFocus();
}
}
}
}
bool StdTransactionEditor::addVatSplit(MyMoneyTransaction& tr, const MyMoneyMoney& amount)
{
if (tr.splitCount() != 2)
return false;
MyMoneyFile* file = MyMoneyFile::instance();
// extract the category split from the transaction
MyMoneyAccount category = file->account(tr.splitByAccount(m_account.id(), false).accountId());
return file->addVATSplit(tr, m_account, category, amount);
}
MyMoneyMoney StdTransactionEditor::removeVatSplit()
{
// we only deal with splits that have three splits
if (m_splits.count() != 2)
return amountFromWidget();
MyMoneySplit c; // category split
MyMoneySplit t; // tax split
bool netValue = false;
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = m_splits.constBegin(); it_s != m_splits.constEnd(); ++it_s) {
MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
if (!acc.value("VatAccount").isEmpty()) {
netValue = (acc.value("VatAmount").toLower() == "net");
c = (*it_s);
} else if (!acc.value("VatRate").isEmpty()) {
t = (*it_s);
}
}
// bail out if not all splits are setup
if (c.id().isEmpty() || t.id().isEmpty())
return amountFromWidget();
MyMoneyMoney amount;
// reduce the splits
if (netValue) {
amount = -c.shares();
} else {
amount = -(c.shares() + t.shares());
}
// remove tax split from the list, ...
m_splits.clear();
m_splits.append(c);
// ... make sure that the widget is updated ...
// block the signals to avoid popping up the split editor dialog
// for nothing
m_editWidgets["category"]->blockSignals(true);
QString id;
setupCategoryWidget(id);
m_editWidgets["category"]->blockSignals(false);
// ... and return the updated amount
return amount;
}
bool StdTransactionEditor::isComplete(QString& reason) const
{
reason.clear();
QMap<QString, QWidget*>::const_iterator it_w;
kMyMoneyDateInput* postDate = dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"]);
if (postDate) {
QDate accountOpeningDate = m_account.openingDate();
for (QList<MyMoneySplit>::const_iterator it_s = m_splits.constBegin(); it_s != m_splits.constEnd(); ++it_s) {
const MyMoneyAccount& acc = MyMoneyFile::instance()->account((*it_s).accountId());
// compute the newest opening date of all accounts involved in the transaction
if (acc.openingDate() > accountOpeningDate)
accountOpeningDate = acc.openingDate();
}
// check the selected category in case m_splits hasn't been updated yet
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
if (category && !category->selectedItem().isEmpty()) {
MyMoneyAccount cat = MyMoneyFile::instance()->account(category->selectedItem());
if (cat.openingDate() > accountOpeningDate)
accountOpeningDate = cat.openingDate();
}
if (postDate->date().isValid() && (postDate->date() < accountOpeningDate)) {
postDate->markAsBadDate(true, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative));
reason = i18n("Cannot enter transaction with postdate prior to account's opening date.");
postDate->setToolTip(reason);
return false;
}
postDate->markAsBadDate();
postDate->setToolTip("");
}
for (it_w = m_editWidgets.begin(); it_w != m_editWidgets.end(); ++it_w) {
KMyMoneyPayeeCombo* payee = dynamic_cast<KMyMoneyPayeeCombo*>(*it_w);
KTagContainer* tagContainer = dynamic_cast<KTagContainer*>(*it_w);
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(*it_w);
kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(*it_w);
KMyMoneyReconcileCombo* reconcile = dynamic_cast<KMyMoneyReconcileCombo*>(*it_w);
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(*it_w);
KTextEdit* memo = dynamic_cast<KTextEdit*>(*it_w);
if (payee && !(payee->currentText().isEmpty()))
break;
if (category && !category->lineEdit()->text().isEmpty())
break;
if (amount && !(amount->value().isZero()))
break;
// the following widgets are only checked if we are editing multiple transactions
if (isMultiSelection()) {
TabBar* tabbar = dynamic_cast<TabBar*>(haveWidget("tabbar"));
if (tabbar) {
tabbar->setEnabled(true);
}
if (reconcile && reconcile->state() != MyMoneySplit::Unknown)
break;
if (cashflow && cashflow->direction() != KMyMoneyRegister::Unknown)
break;
if (postDate->date().isValid() && (postDate->date() >= m_account.openingDate()))
break;
if (memo && m_memoChanged)
break;
if (tagContainer && !(tagContainer->selectedTags().isEmpty())) // Tag is optional field
break;
}
}
return it_w != m_editWidgets.end();
}
void StdTransactionEditor::slotCreateCategory(const QString& name, QString& id)
{
MyMoneyAccount acc, parent;
acc.setName(name);
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"));
if (cashflow) {
// form based input
if (cashflow->direction() == KMyMoneyRegister::Deposit)
parent = MyMoneyFile::instance()->income();
else
parent = MyMoneyFile::instance()->expense();
} else if (haveWidget("deposit")) {
// register based input
kMyMoneyEdit* deposit = dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"]);
if (deposit->value().isPositive())
parent = MyMoneyFile::instance()->income();
else
parent = MyMoneyFile::instance()->expense();
} else
parent = MyMoneyFile::instance()->expense();
// TODO extract possible first part of a hierarchy and check if it is one
// of our top categories. If so, remove it and select the parent
// according to this information.
emit createCategory(acc, parent);
// return id
id = acc.id();
}
int StdTransactionEditor::slotEditSplits()
{
int rc = QDialog::Rejected;
if (!m_openEditSplits) {
// only get in here in a single instance
m_openEditSplits = true;
// force focus change to update all data
QWidget* w = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"])->splitButton();
if (w)
w->setFocus();
kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(haveWidget("amount"));
kMyMoneyEdit* deposit = dynamic_cast<kMyMoneyEdit*>(haveWidget("deposit"));
kMyMoneyEdit* payment = dynamic_cast<kMyMoneyEdit*>(haveWidget("payment"));
KMyMoneyCashFlowCombo* cashflow = 0;
KMyMoneyRegister::CashFlowDirection dir = KMyMoneyRegister::Unknown;
bool isValidAmount = false;
if (amount) {
isValidAmount = amount->lineedit()->text().length() != 0;
cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"));
if (cashflow)
dir = cashflow->direction();
} else {
if (deposit) {
if (deposit->lineedit()->text().length() != 0) {
isValidAmount = true;
dir = KMyMoneyRegister::Deposit;
}
}
if (payment) {
if (payment->lineedit()->text().length() != 0) {
isValidAmount = true;
dir = KMyMoneyRegister::Payment;
}
}
if (!deposit || !payment) {
qDebug("Internal error: deposit(%p) & payment(%p) widgets not found but required", deposit, payment);
return rc;
}
}
if (dir == KMyMoneyRegister::Unknown)
dir = KMyMoneyRegister::Payment;
MyMoneyTransaction transaction;
if (createTransaction(transaction, m_transaction, m_split)) {
MyMoneyMoney value;
QPointer<KSplitTransactionDlg> dlg =
new KSplitTransactionDlg(transaction,
transaction.splits().isEmpty() ? MyMoneySplit() : transaction.splits().front(),
m_account,
isValidAmount,
dir == KMyMoneyRegister::Deposit,
MyMoneyMoney(),
m_priceInfo,
m_regForm);
connect(dlg, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool)));
connect(dlg, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), this, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)));
if ((rc = dlg->exec()) == QDialog::Accepted) {
m_transaction = dlg->transaction();
if (!m_transaction.splits().isEmpty())
m_split = m_transaction.splits().front();
loadEditWidgets();
}
delete dlg;
}
// focus jumps into the tag field
if ((w = haveWidget("tag")) != 0) {
w->setFocus();
}
m_openEditSplits = false;
}
return rc;
}
void StdTransactionEditor::checkPayeeInSplit(MyMoneySplit& s, const QString& payeeId)
{
if (s.accountId().isEmpty())
return;
MyMoneyAccount acc = MyMoneyFile::instance()->account(s.accountId());
if (acc.isIncomeExpense()) {
s.setPayeeId(payeeId);
} else {
if (s.payeeId().isEmpty())
s.setPayeeId(payeeId);
}
}
MyMoneyMoney StdTransactionEditor::amountFromWidget(bool* update) const
{
bool updateValue = false;
MyMoneyMoney value;
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"));
if (cashflow) {
// form based input
kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(m_editWidgets["amount"]);
// if both fields do not contain changes -> no need to update
if (cashflow->direction() != KMyMoneyRegister::Unknown
&& !amount->lineedit()->text().isEmpty())
updateValue = true;
value = amount->value();
if (cashflow->direction() == KMyMoneyRegister::Payment)
value = -value;
} else if (haveWidget("deposit")) {
// register based input
kMyMoneyEdit* deposit = dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"]);
kMyMoneyEdit* payment = dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"]);
// if both fields do not contain text -> no need to update
if (!(deposit->lineedit()->text().isEmpty() && payment->lineedit()->text().isEmpty()))
updateValue = true;
if (deposit->value().isPositive())
value = deposit->value();
else
value = -(payment->value());
}
if (update)
*update = updateValue;
// determine the max fraction for this account and
// adjust the value accordingly
return value.convert(m_account.fraction());
}
bool StdTransactionEditor::createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool skipPriceDialog)
{
// extract price info from original transaction
m_priceInfo.clear();
QList<MyMoneySplit>::const_iterator it_s;
if (!torig.id().isEmpty()) {
for (it_s = torig.splits().begin(); it_s != torig.splits().end(); ++it_s) {
if ((*it_s).id() != sorig.id()) {
MyMoneyAccount cat = MyMoneyFile::instance()->account((*it_s).accountId());
if (cat.currencyId() != m_account.currencyId()) {
if (!(*it_s).shares().isZero() && !(*it_s).value().isZero()) {
m_priceInfo[cat.currencyId()] = ((*it_s).shares() / (*it_s).value()).reduce();
}
}
}
}
}
t = torig;
t.removeSplits();
t.setCommodity(m_account.currencyId());
kMyMoneyDateInput* postDate = dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"]);
if (postDate->date().isValid()) {
t.setPostDate(postDate->date());
}
// we start with the previous values, make sure we can add them later on
MyMoneySplit s0 = sorig;
s0.clearId();
// make sure we reference this account here
s0.setAccountId(m_account.id());
// memo and number field are special: if we have multiple transactions selected
// and the edit field is empty, we treat it as "not modified".
// FIXME a better approach would be to have a 'dirty' flag with the widgets
// which identifies if the originally loaded value has been modified
// by the user
KTextEdit* memo = dynamic_cast<KTextEdit*>(m_editWidgets["memo"]);
if (memo) {
if (!isMultiSelection() || (isMultiSelection() && m_memoChanged))
s0.setMemo(memo->toPlainText());
}
kMyMoneyLineEdit* number = dynamic_cast<kMyMoneyLineEdit*>(haveWidget("number"));
if (number) {
if (!isMultiSelection() || (isMultiSelection() && !number->text().isEmpty()))
s0.setNumber(number->text());
}
KMyMoneyPayeeCombo* payee = dynamic_cast<KMyMoneyPayeeCombo*>(m_editWidgets["payee"]);
QString payeeId;
if (!isMultiSelection() || (isMultiSelection() && !payee->currentText().isEmpty())) {
payeeId = payee->selectedItem();
s0.setPayeeId(payeeId);
}
//KMyMoneyTagCombo* tag = dynamic_cast<KMyMoneyTagCombo*>(m_editWidgets["tag"]);
KTagContainer* tag = dynamic_cast<KTagContainer*>(m_editWidgets["tag"]);
if (!isMultiSelection() || (isMultiSelection() && !tag->selectedTags().isEmpty())) {
s0.setTagIdList(tag->selectedTags());
}
bool updateValue;
MyMoneyMoney value = amountFromWidget(&updateValue);
if (updateValue) {
// for this account, the shares and value is the same
s0.setValue(value);
s0.setShares(value);
} else {
value = s0.value();
}
// if we mark the split reconciled here, we'll use today's date if no reconciliation date is given
KMyMoneyReconcileCombo* status = dynamic_cast<KMyMoneyReconcileCombo*>(m_editWidgets["status"]);
if (status->state() != MyMoneySplit::Unknown)
s0.setReconcileFlag(status->state());
if (s0.reconcileFlag() == MyMoneySplit::Reconciled && !s0.reconcileDate().isValid())
s0.setReconcileDate(QDate::currentDate());
checkPayeeInSplit(s0, payeeId);
// add the split to the transaction
t.addSplit(s0);
// if we have no other split we create it
// if we have none or only one other split, we reconstruct it here
// if we have more than one other split, we take them as they are
// make sure to perform all those changes on a local copy
QList<MyMoneySplit> splits = m_splits;
MyMoneySplit s1;
if (splits.isEmpty()) {
s1.setMemo(s0.memo());
splits.append(s1);
// make sure we will fill the value and share fields later on
updateValue = true;
}
// FIXME in multiSelection we currently only support transactions with one
// or two splits. So we check the original transaction and extract the other
// split or create it
if (isMultiSelection()) {
if (torig.splitCount() == 2) {
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = torig.splits().begin(); it_s != torig.splits().end(); ++it_s) {
if ((*it_s).id() == sorig.id())
continue;
s1 = *it_s;
s1.clearId();
break;
}
}
} else {
if (splits.count() == 1) {
s1 = splits[0];
s1.clearId();
}
}
if (isMultiSelection() || splits.count() == 1) {
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
if (!isMultiSelection() || (isMultiSelection() && !category->currentText().isEmpty())) {
s1.setAccountId(category->selectedItem());
}
// if the first split has a memo but the second split is empty,
// we just copy the memo text over
if (memo) {
if (!isMultiSelection() || (isMultiSelection() && !memo->toPlainText().isEmpty())) {
// if the memo is filled, we check if the
// account referenced by s1 is a regular account or a category.
// in case of a regular account, we just leave the memo as is
// in case of a category we simply copy the new value over the old.
// in case we don't even have an account id, we just skip because
// the split will be removed later on anyway.
if (!s1.memo().isEmpty() && s1.memo() != s0.memo()) {
if (!s1.accountId().isEmpty()) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(s1.accountId());
if (acc.isIncomeExpense())
s1.setMemo(s0.memo());
else if (KMessageBox::questionYesNo(m_regForm,
i18n("Do you want to replace memo<p><i>%1</i></p>with memo<p><i>%2</i></p>in the other split?", s1.memo(), s0.memo()), i18n("Copy memo"),
KStandardGuiItem::yes(), KStandardGuiItem::no(),
QStringLiteral("CopyMemoOver")) == KMessageBox::Yes)
s1.setMemo(s0.memo());
}
} else {
s1.setMemo(s0.memo());
}
}
}
if (updateValue && !s1.accountId().isEmpty()) {
s1.setValue(-value);
MyMoneyMoney shares;
if (!skipPriceDialog) {
if (!KCurrencyCalculator::setupSplitPrice(shares, t, s1, m_priceInfo, m_regForm))
return false;
} else {
MyMoneyAccount cat = MyMoneyFile::instance()->account(s1.accountId());
if (m_priceInfo.find(cat.currencyId()) != m_priceInfo.end()) {
shares = (s1.value() * m_priceInfo[cat.currencyId()]).reduce().convert(cat.fraction());
} else
shares = s1.value();
}
s1.setShares(shares);
}
checkPayeeInSplit(s1, payeeId);
if (!s1.accountId().isEmpty())
t.addSplit(s1);
} else {
QList<MyMoneySplit>::iterator it_s;
for (it_s = splits.begin(); it_s != splits.end(); ++it_s) {
s1 = *it_s;
s1.clearId();
checkPayeeInSplit(s1, payeeId);
t.addSplit(s1);
}
}
return true;
}
void StdTransactionEditor::setupFinalWidgets()
{
addFinalWidget(haveWidget("deposit"));
addFinalWidget(haveWidget("payment"));
addFinalWidget(haveWidget("amount"));
addFinalWidget(haveWidget("status"));
}
void StdTransactionEditor::slotUpdateAccount(const QString& id)
{
TransactionEditor::slotUpdateAccount(id);
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
if (category && category->splitButton()) {
category->splitButton()->setDisabled(id.isEmpty());
}
}
diff --git a/kmymoney/dialogs/transactioneditor.h b/kmymoney/dialogs/transactioneditor.h
index dafdc2f84..bedfa60fe 100644
--- a/kmymoney/dialogs/transactioneditor.h
+++ b/kmymoney/dialogs/transactioneditor.h
@@ -1,443 +1,443 @@
/***************************************************************************
transactioneditor.h
----------
begin : Wed Jun 07 2006
copyright : (C) 2006 by Thomas Baumgart
email : Thomas Baumgart <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 TRANSACTIONEDITOR_H
#define TRANSACTIONEDITOR_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QObject>
#include <QWidget>
#include <QList>
#include <QEvent>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyschedule.h"
#include "transactioneditorcontainer.h"
#include "register.h"
class KMyMoneyCategory;
class TransactionEditor : public QObject
{
Q_OBJECT
public:
TransactionEditor();
TransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::Transaction* item, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate);
virtual ~TransactionEditor();
/**
* This method is used as a helper because virtual methods cannot be
* called within a constructor. Thus setup() should be called immediately
* after a TransactionEditor() object or one of its derivatives is
* constructed. The parameter @a account identifies the account that
* is currently opened in the calling ledger view.
*
* This account will not be included in category sets. The default is
* no account so all will be shown. I have no idea anymore, what I
* tried to say with the first sentence above. :( Maybe this is crap.
*
* @param tabOrderWidgets QWidgetList which will be filled with the pointers
* to the editWidgets in their tab order
* @param account account that is currently shown in the calling ledger view
* @param action default action (defaults to ActionNone).
*/
void setup(QWidgetList& tabOrderWidgets, const MyMoneyAccount& account = MyMoneyAccount(), KMyMoneyRegister::Action action = KMyMoneyRegister::ActionNone);
/**
* Enter the transactions into the ledger. In case of a newly created
* transaction @a newId contains the assigned id. In case @a askForSchedule
* is true (the default), the user will be asked if he wants to enter new
* transactions with a post date in the future into the ledger or rather
* create a schedule for them. In case @a suppressBalanceWarnings is @p false
* (the default) a warning will be displayed when the balance crosses the minimum
* or maximum balance settings for the account.
*/
virtual bool enterTransactions(QString& newId, bool askForSchedule = true, bool suppressBalanceWarnings = false);
/**
* This method creates a transaction based on the contents of the current widgets,
* the splits in m_split in single selection mode or an existing transaction/split
* and the contents of the widgets in multi selection mode.
*
* The split referencing the current account is returned as the first split in the
* transaction's split list.
*
* @param t reference to created transaction
* @param torig the original transaction
* @param sorig the original split
* @param skipPriceDialog if @p true the user will not be requested for price information
* (defaults to @p false)
*
* @return @p false if aborted by user, @p true otherwise
*
* @note Usually not used directly. If unsure, use enterTransactions() instead.
*/
virtual bool createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool skipPriceDialog = false) = 0;
/**
* This method returns information about the completeness of the data
* entered. This can be used to control the availability of the
* 'Enter transaction' action.
*
* @retval true if entering the transaction into the engine
* @retval false if not enough information is present to enter the
* transaction into the engine
*
* @param reason will be filled with a string about the reason why the
* completeness is not reached. Empty if the return value
* is @c true.
*
* @sa transactionDataSufficient()
*/
virtual bool isComplete(QString& reason) const = 0;
/**
* This method returns information if the editor is started with multiple transactions
* being selected or not.
*
* @retval false only a single transaction was selected when the editor was started
* @retval true multiple transactions were selected when the editor was started
*/
virtual bool isMultiSelection() const {
return m_transactions.count() > 1;
}
virtual bool fixTransactionCommodity(const MyMoneyAccount& account);
virtual bool canAssignNumber() const;
virtual void assignNextNumber();
/**
* Returns a pointer to the widget that should receive
* the focus after the editor has been started.
*/
virtual QWidget* firstWidget() const = 0;
/**
* Returns a pointer to a widget by name
*/
QWidget* haveWidget(const QString& name) const;
void setTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s);
bool eventFilter(QObject* o, QEvent* e);
const MyMoneyAccount& account() const {
return m_account;
}
void clearFinalWidgets();
void addFinalWidget(const QWidget*);
QString m_memoText;
QString m_scheduleInfo;
- MyMoneySchedule::paymentTypeE m_paymentMethod;
+ eMyMoney::Schedule::PaymentType m_paymentMethod;
public slots:
void slotReloadEditWidgets();
/**
* The default implementation returns QDialog::Rejected
*/
virtual int slotEditSplits();
/**
* Modify the account which the transaction should be based on. The
* initial value for the account is passed during setup().
*
* @param id of the account to be used
*/
virtual void slotUpdateAccount(const QString& id);
protected:
virtual void createEditWidgets() = 0;
virtual void setupFinalWidgets() = 0;
virtual void loadEditWidgets(KMyMoneyRegister::Action action = KMyMoneyRegister::ActionNone) = 0;
void setupCategoryWidget(KMyMoneyCategory* category, const QList<MyMoneySplit>& splits, QString& categoryId, const char* splitEditSlot, bool allowObjectCreation = true);
void resizeForm();
/**
* This method sets the precision of the value widgets to reflect
* the account in m_account. If m_account has no id, the precision
* defaults to 2.
*/
void setupPrecision();
protected slots:
void slotUpdateButtonState();
void slotUpdateMemoState();
void slotUpdateAccount();
void slotNumberChanged(const QString&);
signals:
/**
* This signal is sent out by the destructor to inform other entities
* that editing has been finished. The parameter @a t contains the list
* of transactions that were processed.
*/
void finishEdit(const KMyMoneyRegister::SelectedTransactions& t);
/**
* This signal is sent out whenever enough data is present to enter the
* transaction into the ledger. This signal can be used to control the
* KAction which implements entering the transaction.
*
* @sa isComplete()
*
* @param state @a true if enough data is present, @a false otherwise.
*/
void transactionDataSufficient(bool state);
/**
* This signal is sent out, when a new payee needs to be created
* @sa KMyMoneyCombo::createItem()
*
* @param txt The name of the payee to be created
* @param id A connected slot should store the id of the created object in this variable
*/
void createPayee(const QString& txt, QString& id);
/**
* This signal is sent out, when a new category needs to be created
* Depending on the setting of either a payment or deposit, the parent
* account will be preset to Expense or Income.
*
* @param account reference to account info. Will be filled by called slot
* @param parent reference to parent account
*/
void createCategory(MyMoneyAccount& account, const MyMoneyAccount& parent);
/**
* This signal is sent out, when a new tag needs to be created
* @param txt The name of the tag to be created
* @param id A connected slot should store the id of the created object in this variable
*/
void createTag(const QString& txt, QString& id);
/**
* This signal is sent out, when a new security (e.g. stock )needs to be created
* @a Parent should be the investment account under which the security account
* will be created.
*
* @param account reference to account info. Will be filled by called slot
* @param parent reference to parent account
*/
void createSecurity(MyMoneyAccount& account, const MyMoneyAccount& parent);
/**
* Signal is emitted, if any of the widgets enters (@a state equals @a true)
* or leaves (@a state equals @a false) object creation mode.
*
* @param state Enter (@a true) or leave (@a false) object creation
*/
void objectCreation(bool state);
void statusMsg(const QString& txt);
void statusProgress(int cnt, int base);
/**
* This signal is sent out for each newly added transaction
*
* @param date the post date of the newly created transaction
*/
void lastPostDateUsed(const QDate& date);
/**
* This signal is sent out, if the user decides to schedule the transaction @a t
* rather then adding it to the ledger right away.
*/
- void scheduleTransaction(const MyMoneyTransaction& t, MyMoneySchedule::occurrenceE occurrence);
+ void scheduleTransaction(const MyMoneyTransaction& t, eMyMoney::Schedule::Occurrence occurrence);
/**
* This signal is sent out, if the user double clicks the number field
*/
void assignNumber();
/**
* This signal is sent out, if the user has pressed the ESC key.
*/
void escapePressed();
/**
* This signal is sent out, if the user has pressed the Return or Enter
* key and asks to end editing the transaction
*/
void returnPressed();
/**
* This signal is sent out, if any of the balance warning levels
* for @p account has been reached. @p msg contains the message text.
* @p parent points to the parent widget to be used for the warning message box.
*/
void balanceWarning(QWidget* parent, const MyMoneyAccount& account, const QString& msg);
void operationTypeChanged(int index);
protected:
QList<MyMoneySplit> m_splits;
KMyMoneyRegister::SelectedTransactions m_transactions;
QList<const QWidget*> m_finalEditWidgets;
TransactionEditorContainer* m_regForm;
KMyMoneyRegister::Transaction* m_item;
KMyMoneyRegister::QWidgetContainer m_editWidgets;
MyMoneyAccount m_account;
MyMoneyTransaction m_transaction;
MyMoneySplit m_split;
QDate m_lastPostDate;
QMap<QString, MyMoneyMoney> m_priceInfo;
KMyMoneyRegister::Action m_initialAction;
bool m_openEditSplits;
bool m_memoChanged;
private:
/**
* If a new or an edited transaction has a valid number, keep it with the account
*/
void keepNewNumber(const MyMoneyTransaction& tr);
};
class StdTransactionEditor : public TransactionEditor
{
Q_OBJECT
public:
StdTransactionEditor();
StdTransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::Transaction* item, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate);
~StdTransactionEditor();
bool isComplete(QString& reason) const;
QWidget* firstWidget() const;
/**
* This method creates a transaction based on the contents of the current widgets,
* the splits in m_split in single selection mode or an existing transaction/split
* and the contents of the widgets in multi selection mode.
*
* The split referencing the current account is returned as the first split in the
* transaction's split list.
*
* @param t reference to created transaction
* @param torig the original transaction
* @param sorig the original split
* @param skipPriceDialog if @p true the user will not be requested for price information
* (defaults to @p false)
*
* @return @p false if aborted by user, @p true otherwise
*
* @note Usually not used directly. If unsure, use enterTransactions() instead.
*/
bool createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool skipPriceDialog = false);
public slots:
int slotEditSplits();
void slotUpdateAmount(const QString&);
protected slots:
void slotReloadEditWidgets();
void slotUpdatePayment(const QString&);
void slotUpdateDeposit(const QString&);
void slotUpdateCategory(const QString&);
void slotUpdatePayee(const QString&);
//void slotUpdateTag(const QString&);
void slotUpdateCashFlow(KMyMoneyRegister::CashFlowDirection);
void slotCreateCategory(const QString&, QString&);
void slotUpdateAction(int action);
void slotUpdateAccount(const QString& id);
protected:
/**
* This method creates all necessary widgets for this transaction editor.
* All signals will be connected to the relevant slots.
*/
void createEditWidgets();
/**
* This method (re-)loads the widgets with the transaction information
* contained in @a m_transaction and @a m_split.
*
* @param action preset the edit wigdets for @a action if no transaction
* is present
*/
void loadEditWidgets(KMyMoneyRegister::Action action = KMyMoneyRegister::ActionNone);
void setupCategoryWidget(QString&);
void updateAmount(const MyMoneyMoney& value);
bool isTransfer(const QString& accId1, const QString& accId2) const;
void checkPayeeInSplit(MyMoneySplit& s, const QString& payeeId);
/**
* This method fills the editor widgets with the last transaction
* that can be found for payee @a payeeId in the account @a m_account.
*/
void autoFill(const QString& payeeId);
/**
* Extracts the amount of the transaction from the widgets depending
* if form or register based input method is used.
* Returns if an amount has been found in @a update.
*
* @param update pointer to update information flag
* @return amount of transaction (deposit positive, payment negative)
*/
MyMoneyMoney amountFromWidget(bool* update = 0) const;
/**
* Create or update a VAT split
*/
void updateVAT(bool amountChanged = true);
MyMoneyMoney removeVatSplit();
/**
* This method adds a VAT split to transaction @a tr if necessary.
*
* @param tr transaction that the split should be added to
* @param amount Amount to be used for the calculation. Depending upon the
* setting of the resp. category, this value is treated as
* either gross or net value.
* @retval false VAT split has not been added
* @retval true VAT split has been added
*/
bool addVatSplit(MyMoneyTransaction& tr, const MyMoneyMoney& amount);
void setupFinalWidgets();
/**
* This method returns the sum of all splits of transaction @a t that
* reference account m_account.
*/
MyMoneyMoney shares(const MyMoneyTransaction& t) const;
private:
MyMoneyMoney m_shares;
bool m_inUpdateVat;
};
#endif
diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp
index 7186c9735..c9102bc4a 100644
--- a/kmymoney/kmymoney.cpp
+++ b/kmymoney/kmymoney.cpp
@@ -1,7582 +1,7584 @@
/***************************************************************************
kmymoney.cpp
-------------------
copyright : (C) 2000 by Michael Edwardes <mte@users.sourceforge.net>
(C) 2007 by Thomas Baumgart <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 "config-kmymoney.h"
#include "kmymoney.h"
// for _getpid
#ifdef Q_OS_WIN32 //krazy:exclude=cpp
#include <process.h>
#else
#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#endif
// ----------------------------------------------------------------------------
// Std C++ / STL Includes
#include <typeinfo>
#include <iostream>
#include <memory>
// ----------------------------------------------------------------------------
// QT Includes
#include <QDir>
#include <QDateTime> // only for performance tests
#include <QTimer>
#include <QByteArray>
#include <QBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QProgressBar>
#include <QList>
#include <QUrl>
#include <QClipboard>
#include <QKeySequence>
#include <QIcon>
#include <QInputDialog>
#include <QStatusBar>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KToolBar>
#include <KMessageBox>
#include <KLocalizedString>
#include <KConfig>
#include <KStandardAction>
#include <KActionCollection>
#include <KTipDialog>
#include <KRun>
#include <KConfigDialog>
#include <KXMLGUIFactory>
#include <KRecentFilesAction>
#include <KRecentDirs>
#include <KProcess>
#include <KAboutApplicationDialog>
#include <KPluginMetaData>
#include <KPluginLoader>
#ifdef KF5Holidays_FOUND
#include <KHolidays/Holiday>
#include <KHolidays/HolidayRegion>
#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/ktagreassigndlg.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/onlinejobmessagesview.h"
#include "widgets/kmymoneymvccombo.h"
#include "views/kmymoneyview.h"
#include "views/konlinejoboutbox.h"
#include "models/onlinejobmessagesmodel.h"
+#include "mymoney/mymoneyfile.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/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/mymoneystoragesql.h"
#include <libkgpgfile/kgpgfile.h>
#include "transactioneditor.h"
#include "konlinetransferform.h"
#include <QHBoxLayout>
#include <QFileDialog>
#include "kmymoneyutils.h"
#include "kcreditswindow.h"
#include "ledgerdelegate.h"
#include "storageenums.h"
+#include "mymoneyenums.h"
using namespace Icons;
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
const QHash<Action, QString> KMyMoneyApp::s_Actions {
{Action::FileOpenDatabase, QStringLiteral("open_database")},
{Action::FileSaveAsDatabase, QStringLiteral("saveas_database")},
{Action::FileBackup, QStringLiteral("file_backup")},
{Action::FileImportGNC, QStringLiteral("file_import_gnc")},
{Action::FileImportStatement, QStringLiteral("file_import_statement")},
{Action::FileImportTemplate, QStringLiteral("file_import_template")},
{Action::FileExportTemplate, QStringLiteral("file_export_template")},
{Action::FilePersonalData, QStringLiteral("view_personal_data")},
#ifdef KMM_DEBUG
{Action::FileDump, QStringLiteral("file_dump")},
#endif
{Action::FileInformation, QStringLiteral("view_file_info")},
{Action::EditFindTransaction, QStringLiteral("edit_find_transaction")},
{Action::ViewTransactionDetail, QStringLiteral("view_show_transaction_detail")},
{Action::ViewHideReconciled, QStringLiteral("view_hide_reconciled_transactions")},
{Action::ViewHideCategories, QStringLiteral("view_hide_unused_categories")},
{Action::ViewShowAll, QStringLiteral("view_show_all_accounts")},
{Action::InstitutionNew, QStringLiteral("institution_new")},
{Action::InstitutionEdit, QStringLiteral("institution_edit")},
{Action::InstitutionDelete, QStringLiteral("institution_delete")},
{Action::AccountNew, QStringLiteral("account_new")},
{Action::AccountOpen, QStringLiteral("account_open")},
{Action::AccountStartReconciliation, QStringLiteral("account_reconcile")},
{Action::AccountFinishReconciliation, QStringLiteral("account_reconcile_finish")},
{Action::AccountPostponeReconciliation, QStringLiteral("account_reconcile_postpone")},
{Action::AccountEdit, QStringLiteral("account_edit")},
{Action::AccountDelete, QStringLiteral("account_delete")},
{Action::AccountClose, QStringLiteral("account_close")},
{Action::AccountReopen, QStringLiteral("account_reopen")},
{Action::AccountTransactionReport, QStringLiteral("account_transaction_report")},
{Action::AccountBalanceChart, QStringLiteral("account_chart")},
{Action::AccountOnlineMap, QStringLiteral("account_online_map")},
{Action::AccountOnlineUnmap, QStringLiteral("account_online_unmap")},
{Action::AccountUpdateMenu, QStringLiteral("account_online_update_menu")},
{Action::AccountUpdate, QStringLiteral("account_online_update")},
{Action::AccountUpdateAll, QStringLiteral("account_online_update_all")},
{Action::AccountCreditTransfer, QStringLiteral("account_online_new_credit_transfer")},
{Action::CategoryNew, QStringLiteral("category_new")},
{Action::CategoryEdit, QStringLiteral("category_edit")},
{Action::CategoryDelete, QStringLiteral("category_delete")},
{Action::ToolCurrencies, QStringLiteral("tools_currency_editor")},
{Action::ToolPrices, QStringLiteral("tools_price_editor")},
{Action::ToolUpdatePrices, QStringLiteral("tools_update_prices")},
{Action::ToolConsistency, QStringLiteral("tools_consistency_check")},
{Action::ToolPerformance, QStringLiteral("tools_performancetest")},
{Action::ToolSQL, QStringLiteral("tools_generate_sql")},
{Action::ToolCalculator, QStringLiteral("tools_kcalc")},
{Action::SettingsAllMessages, QStringLiteral("settings_enable_messages")},
{Action::HelpShow, QStringLiteral("help_show_tip")},
{Action::TransactionNew, QStringLiteral("transaction_new")},
{Action::TransactionEdit, QStringLiteral("transaction_edit")},
{Action::TransactionEnter, QStringLiteral("transaction_enter")},
{Action::TransactionEditSplits, QStringLiteral("transaction_editsplits")},
{Action::TransactionCancel, QStringLiteral("transaction_cancel")},
{Action::TransactionDelete, QStringLiteral("transaction_delete")},
{Action::TransactionDuplicate, QStringLiteral("transaction_duplicate")},
{Action::TransactionMatch, QStringLiteral("transaction_match")},
{Action::TransactionAccept, QStringLiteral("transaction_accept")},
{Action::TransactionToggleReconciled, QStringLiteral("transaction_mark_toggle")},
{Action::TransactionToggleCleared, QStringLiteral("transaction_mark_cleared")},
{Action::TransactionReconciled, QStringLiteral("transaction_mark_reconciled")},
{Action::TransactionNotReconciled, QStringLiteral("transaction_mark_notreconciled")},
{Action::TransactionSelectAll, QStringLiteral("transaction_select_all")},
{Action::TransactionGoToAccount, QStringLiteral("transaction_goto_account")},
{Action::TransactionGoToPayee, QStringLiteral("transaction_goto_payee")},
{Action::TransactionCreateSchedule, QStringLiteral("transaction_create_schedule")},
{Action::TransactionAssignNumber, QStringLiteral("transaction_assign_number")},
{Action::TransactionCombine, QStringLiteral("transaction_combine")},
{Action::TransactionCopySplits, QStringLiteral("transaction_copy_splits")},
{Action::TransactionMoveMenu, QStringLiteral("transaction_move_menu")},
{Action::TransactionMarkMenu, QStringLiteral("transaction_mark_menu")},
{Action::TransactionContextMarkMenu, QStringLiteral("transaction_context_mark_menu")},
{Action::InvestmentNew, QStringLiteral("investment_new")},
{Action::InvestmentEdit, QStringLiteral("investment_edit")},
{Action::InvestmentDelete, QStringLiteral("investment_delete")},
{Action::InvestmentOnlinePrice, QStringLiteral("investment_online_price_update")},
{Action::InvestmentManualPrice, QStringLiteral("investment_manual_price_update")},
{Action::ScheduleNew, QStringLiteral("schedule_new")},
{Action::ScheduleEdit, QStringLiteral("schedule_edit")},
{Action::ScheduleDelete, QStringLiteral("schedule_delete")},
{Action::ScheduleDuplicate, QStringLiteral("schedule_duplicate")},
{Action::ScheduleEnter, QStringLiteral("schedule_enter")},
{Action::ScheduleSkip, QStringLiteral("schedule_skip")},
{Action::PayeeNew, QStringLiteral("payee_new")},
{Action::PayeeRename, QStringLiteral("payee_rename")},
{Action::PayeeDelete, QStringLiteral("payee_delete")},
{Action::PayeeMerge, QStringLiteral("payee_merge")},
{Action::TagNew, QStringLiteral("tag_new")},
{Action::TagRename, QStringLiteral("tag_rename")},
{Action::TagDelete, QStringLiteral("tag_delete")},
{Action::BudgetNew, QStringLiteral("budget_new")},
{Action::BudgetRename, QStringLiteral("budget_rename")},
{Action::BudgetDelete, QStringLiteral("budget_delete")},
{Action::BudgetCopy, QStringLiteral("budget_copy")},
{Action::BudgetChangeYear, QStringLiteral("budget_change_year")},
{Action::BudgetForecast, QStringLiteral("budget_forecast")},
{Action::CurrencyNew, QStringLiteral("currency_new")},
{Action::CurrencyRename, QStringLiteral("currency_rename")},
{Action::CurrencyDelete, QStringLiteral("currency_delete")},
{Action::CurrencySetBase, QStringLiteral("currency_setbase")},
{Action::PriceNew, QStringLiteral("price_new")},
{Action::PriceEdit, QStringLiteral("price_edit")},
{Action::PriceUpdate, QStringLiteral("price_update")},
{Action::PriceDelete, QStringLiteral("price_delete")},
#ifdef KMM_DEBUG
{Action::WizardNewUser, QStringLiteral("new_user_wizard")},
{Action::DebugTraces, QStringLiteral("debug_traces")},
#endif
{Action::DebugTimers, QStringLiteral("debug_timers")},
{Action::OnlineJobDelete, QStringLiteral("onlinejob_delete")},
{Action::OnlineJobEdit, QStringLiteral("onlinejob_edit")},
{Action::OnlineJobLog, QStringLiteral("onlinejob_log")},
};
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_pluginLoader(0),
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<QPair<MyMoneyTransaction, MyMoneySplit> > automaticReconciliation(const MyMoneyAccount &account,
const QList<QPair<MyMoneyTransaction, MyMoneySplit> > &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;
KMyMoneyPlugin::PluginLoader* m_pluginLoader;
QString m_lastPayeeEnteredId;
/** the configuration object of the application */
KSharedConfigPtr m_config;
/**
* @brief List of all plugged plugins
*
* The key is the file name of the plugin.
*/
QMap<QString, KMyMoneyPlugin::Plugin*> m_plugins;
/**
* @brief List of plugged importer plugins
*
* The key is the objectName of the plugin.
*/
QMap<QString, KMyMoneyPlugin::ImporterPlugin*> m_importerPlugins;
/**
* @brief List of plugged online plugins
*
* The key is the objectName of the plugin.
*/
QMap<QString, KMyMoneyPlugin::OnlinePlugin*> m_onlinePlugins;
/**
* 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<QString> m_importUrlsQueue;
KFindTransactionDlg* m_searchDlg;
QObject* m_pluginInterface;
MyMoneyAccount m_selectedAccount;
MyMoneyAccount m_reconciliationAccount;
MyMoneyAccount m_selectedInvestment;
MyMoneyInstitution m_selectedInstitution;
MyMoneySchedule m_selectedSchedule;
MyMoneySecurity m_selectedCurrency;
MyMoneyPrice m_selectedPrice;
QList<MyMoneyPayee> m_selectedPayees;
QList<MyMoneyTag> m_selectedTags;
QList<MyMoneyBudget> m_selectedBudgets;
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<QDate, bool> 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>("MyMoneyMoney");
qRegisterMetaType<MyMoneySecurity>("MyMoneySecurity");
// 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();
initActions();
initDynamicMenus();
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()));
connectActionsAndViews();
///////////////////////////////////////////////////////////////////
// call inits to invoke all other construction parts
readOptions();
// now initialize the plugin structure
createInterfaces();
loadPlugins();
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(actionCollection()->action(s_Actions[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();
// 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
unloadPlugins();
delete d->m_searchDlg;
delete d->m_transactionEditor;
delete d->m_endingBalanceDlg;
delete d->m_moveToAccountSelector;
#ifdef KF5Holidays_FOUND
delete d->m_holidayRegion;
#endif
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<QListWidget *>()) {
// 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();
}
}
}
void KMyMoneyApp::createTransactionMoveMenu()
{
if (!d->m_moveToAccountSelector) {
QWidget* w = factory()->container("transaction_move_menu", this);
QMenu *menu = dynamic_cast<QMenu*>(w);
if (menu) {
QWidgetAction *accountSelectorAction = new QWidgetAction(menu);
d->m_moveToAccountSelector = new kMyMoneyAccountSelector(menu, 0, false);
d->m_moveToAccountSelector->setObjectName("transaction_move_menu_selector");
accountSelectorAction->setDefaultWidget(d->m_moveToAccountSelector);
menu->addAction(accountSelectorAction);
connect(d->m_moveToAccountSelector, SIGNAL(destroyed(QObject*)), this, SLOT(slotObjectDestroyed(QObject*)));
connect(d->m_moveToAccountSelector, SIGNAL(itemSelected(QString)), this, SLOT(slotMoveToAccount(QString)));
}
}
}
void KMyMoneyApp::initDynamicMenus()
{
connect(this, SIGNAL(accountSelected(MyMoneyAccount)), this, SLOT(slotUpdateMoveToAccountMenu()));
connect(this, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotUpdateMoveToAccountMenu()));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotUpdateMoveToAccountMenu()));
}
void KMyMoneyApp::initActions()
{
KActionCollection *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. overlaid icon or keyboard shortcut
*/
QHash<Action, QAction *> lutActions;
// *************
// Adding all actions
// *************
{
// struct for QAction's vital (except icon) informations
struct actionInfo {
Action action;
KMyMoneyAppFunc callback;
QString text;
Icon icon;
};
const QVector<actionInfo> actionInfos {
// *************
// The File menu
// *************
{Action::FileOpenDatabase, &KMyMoneyApp::slotOpenDatabase, i18n("Open database..."), Icon::SVNUpdate},
{Action::FileSaveAsDatabase, &KMyMoneyApp::slotSaveAsDatabase, i18n("Save as database..."), Icon::FileArchiver},
{Action::FileBackup, &KMyMoneyApp::slotBackupFile, i18n("Backup..."), Icon::Empty},
{Action::FileImportGNC, &KMyMoneyApp::slotGncImport, i18n("GnuCash..."), Icon::Empty},
{Action::FileImportStatement, &KMyMoneyApp::slotStatementImport, i18n("Statement file..."), Icon::Empty},
{Action::FileImportTemplate, &KMyMoneyApp::slotLoadAccountTemplates, i18n("Account Template..."), Icon::Empty},
{Action::FileExportTemplate, &KMyMoneyApp::slotSaveAccountTemplates, i18n("Account Template..."), Icon::Empty},
{Action::FilePersonalData, &KMyMoneyApp::slotFileViewPersonal, i18n("Personal Data..."), Icon::UserProperties},
#ifdef KMM_DEBUG
{Action::FileDump, &KMyMoneyApp::slotFileFileInfo, i18n("Dump Memory"), Icon::Empty},
#endif
{Action::FileInformation, &KMyMoneyApp::slotFileInfoDialog, i18n("File-Information..."), Icon::DocumentProperties},
// *************
// The Edit menu
// *************
{Action::EditFindTransaction, &KMyMoneyApp::slotFindTransaction, i18n("Find transaction..."), Icon::Empty},
// *************
// The View menu
// *************
{Action::ViewTransactionDetail, &KMyMoneyApp::slotShowTransactionDetail, i18n("Show Transaction Detail"), Icon::ViewTransactionDetail},
{Action::ViewHideReconciled, &KMyMoneyApp::slotHideReconciledTransactions, i18n("Hide reconciled transactions"), Icon::HideReconciled},
{Action::ViewHideCategories, &KMyMoneyApp::slotHideUnusedCategories, i18n("Hide unused categories"), Icon::HideCategories},
{Action::ViewShowAll, &KMyMoneyApp::slotShowAllAccounts, i18n("Show all accounts"), Icon::Empty},
// *********************
// The institutions menu
// *********************
{Action::InstitutionNew, &KMyMoneyApp::slotInstitutionNew, i18n("New institution..."), Icon::Empty},
{Action::InstitutionEdit, &KMyMoneyApp::slotInstitutionEditEmpty, i18n("Edit institution..."), Icon::Empty},
{Action::InstitutionDelete, &KMyMoneyApp::slotInstitutionDelete, i18n("Delete institution..."), Icon::Empty},
// *****************
// The accounts menu
// *****************
{Action::AccountNew, &KMyMoneyApp::slotAccountNew, i18n("New account..."), Icon::Empty},
{Action::AccountOpen, &KMyMoneyApp::slotAccountOpenEmpty, i18n("Open ledger"), Icon::ViewFinancialList},
{Action::AccountStartReconciliation, &KMyMoneyApp::slotAccountReconcileStart, i18n("Reconcile..."), Icon::Reconcile},
{Action::AccountFinishReconciliation, &KMyMoneyApp::slotAccountReconcileFinish, i18nc("Finish reconciliation", "Finish"), Icon::Empty},
{Action::AccountPostponeReconciliation, &KMyMoneyApp::slotAccountReconcilePostpone, i18n("Postpone reconciliation"), Icon::MediaPlaybackPause},
{Action::AccountEdit, &KMyMoneyApp::slotAccountEdit, i18n("Edit account..."), Icon::Empty},
{Action::AccountDelete, &KMyMoneyApp::slotAccountDelete, i18n("Delete account..."), Icon::Empty},
{Action::AccountClose, &KMyMoneyApp::slotAccountClose, i18n("Close account"), Icon::Empty},
{Action::AccountReopen, &KMyMoneyApp::slotAccountReopen, i18n("Reopen account"), Icon::Empty},
{Action::AccountTransactionReport, &KMyMoneyApp::slotAccountTransactionReport, i18n("Transaction report"), Icon::ViewFinancialList},
{Action::AccountBalanceChart, &KMyMoneyApp::slotAccountChart, i18n("Show balance chart..."), Icon::OfficeChartLine},
{Action::AccountOnlineMap, &KMyMoneyApp::slotAccountMapOnline, i18n("Map account..."), Icon::NewsSubscribe},
{Action::AccountOnlineUnmap, &KMyMoneyApp::slotAccountUnmapOnline, i18n("Unmap account..."), Icon::NewsUnsubscribe},
{Action::AccountUpdateMenu, &KMyMoneyApp::slotAccountUpdateOnline, i18nc("Update online accounts menu", "Update"), Icon::Empty},
{Action::AccountUpdate, &KMyMoneyApp::slotAccountUpdateOnline, i18n("Update account..."), Icon::Empty},
{Action::AccountUpdateAll, &KMyMoneyApp::slotAccountUpdateOnlineAll, i18n("Update all accounts..."), Icon::Empty},
{Action::AccountCreditTransfer, &KMyMoneyApp::slotNewOnlineTransfer, i18n("New credit transfer"), Icon::Empty},
// *******************
// The categories menu
// *******************
{Action::CategoryNew, &KMyMoneyApp::slotCategoryNew, i18n("New category..."), Icon::Empty},
{Action::CategoryEdit, &KMyMoneyApp::slotAccountEdit, i18n("Edit category..."), Icon::Empty},
{Action::CategoryDelete, &KMyMoneyApp::slotAccountDelete, i18n("Delete category..."), Icon::Empty},
// **************
// The tools menu
// **************
{Action::ToolCurrencies, &KMyMoneyApp::slotCurrencyDialog, i18n("Currencies..."), Icon::ViewCurrencyList},
{Action::ToolPrices, &KMyMoneyApp::slotPriceDialog, i18n("Prices..."), Icon::Empty},
{Action::ToolUpdatePrices, &KMyMoneyApp::slotEquityPriceUpdate, i18n("Update Stock and Currency Prices..."), Icon::Empty},
{Action::ToolConsistency, &KMyMoneyApp::slotFileConsistencyCheck, i18n("Consistency Check"), Icon::Empty},
{Action::ToolPerformance, &KMyMoneyApp::slotPerformanceTest, i18n("Performance-Test"), Icon::Fork},
{Action::ToolSQL, &KMyMoneyApp::slotGenerateSql, i18n("Generate Database SQL"), Icon::Empty},
{Action::ToolCalculator, &KMyMoneyApp::slotToolsStartKCalc, i18n("Calculator..."), Icon::AccessoriesCalculator},
// *****************
// The settings menu
// *****************
{Action::SettingsAllMessages, &KMyMoneyApp::slotEnableMessages, i18n("Enable all messages"), Icon::Empty},
// *************
// The help menu
// *************
{Action::HelpShow, &KMyMoneyApp::slotShowTipOfTheDay, i18n("&Show tip of the day"), Icon::Tip},
// ***************************
// Actions w/o main menu entry
// ***************************
{Action::TransactionNew, &KMyMoneyApp::slotTransactionsNew, i18nc("New transaction button", "New"), Icon::Empty},
{Action::TransactionEdit, &KMyMoneyApp::slotTransactionsEdit, i18nc("Edit transaction button", "Edit"), Icon::Empty},
{Action::TransactionEnter, &KMyMoneyApp::slotTransactionsEnter, i18nc("Enter transaction", "Enter"), Icon::DialogOK},
{Action::TransactionEditSplits, &KMyMoneyApp::slotTransactionsEditSplits, i18nc("Edit split button", "Edit splits"), Icon::Split},
{Action::TransactionCancel, &KMyMoneyApp::slotTransactionsCancel, i18nc("Cancel transaction edit", "Cancel"), Icon::DialogCancel},
{Action::TransactionDelete, &KMyMoneyApp::slotTransactionsDelete, i18nc("Delete transaction", "Delete"), Icon::EditDelete},
{Action::TransactionDuplicate, &KMyMoneyApp::slotTransactionDuplicate, i18nc("Duplicate transaction", "Duplicate"), Icon::EditCopy},
{Action::TransactionMatch, &KMyMoneyApp::slotTransactionMatch, i18nc("Button text for match transaction", "Match"),Icon::Empty},
{Action::TransactionAccept, &KMyMoneyApp::slotTransactionsAccept, i18nc("Accept 'imported' and 'matched' transaction", "Accept"), Icon::Empty},
{Action::TransactionToggleReconciled, &KMyMoneyApp::slotToggleReconciliationFlag, i18nc("Toggle reconciliation flag", "Toggle"), Icon::Empty},
{Action::TransactionToggleCleared, &KMyMoneyApp::slotMarkTransactionCleared, i18nc("Mark transaction cleared", "Cleared"), Icon::Empty},
{Action::TransactionReconciled, &KMyMoneyApp::slotMarkTransactionReconciled, i18nc("Mark transaction reconciled", "Reconciled"), Icon::Empty},
{Action::TransactionNotReconciled, &KMyMoneyApp::slotMarkTransactionNotReconciled, i18nc("Mark transaction not reconciled", "Not reconciled"), Icon::Empty},
{Action::TransactionSelectAll, &KMyMoneyApp::selectAllTransactions, i18nc("Select all transactions", "Select all"), Icon::Empty},
{Action::TransactionGoToAccount, &KMyMoneyApp::slotTransactionGotoAccount, i18n("Go to account"), Icon::GoJump},
{Action::TransactionGoToPayee, &KMyMoneyApp::slotTransactionGotoPayee, i18n("Go to payee"), Icon::GoJump},
{Action::TransactionCreateSchedule, &KMyMoneyApp::slotTransactionCreateSchedule, i18n("Create scheduled transaction..."), Icon::AppointmentNew},
{Action::TransactionAssignNumber, &KMyMoneyApp::slotTransactionAssignNumber, i18n("Assign next number"), Icon::Empty},
{Action::TransactionCombine, &KMyMoneyApp::slotTransactionCombine, i18nc("Combine transactions", "Combine"), Icon::Empty},
{Action::TransactionCopySplits, &KMyMoneyApp::slotTransactionCopySplits, i18n("Copy splits"), Icon::Empty},
//Investment
{Action::InvestmentNew, &KMyMoneyApp::slotInvestmentNew, i18n("New investment..."), Icon::Empty},
{Action::InvestmentEdit, &KMyMoneyApp::slotInvestmentEdit, i18n("Edit investment..."), Icon::Empty},
{Action::InvestmentDelete, &KMyMoneyApp::slotInvestmentDelete, i18n("Delete investment..."), Icon::Empty},
{Action::InvestmentOnlinePrice, &KMyMoneyApp::slotOnlinePriceUpdate, i18n("Online price update..."), Icon::Empty},
{Action::InvestmentManualPrice, &KMyMoneyApp::slotManualPriceUpdate, i18n("Manual price update..."), Icon::Empty},
//Schedule
{Action::ScheduleNew, &KMyMoneyApp::slotScheduleNew, i18n("New scheduled transaction"), Icon::AppointmentNew},
{Action::ScheduleEdit, &KMyMoneyApp::slotScheduleEdit, i18n("Edit scheduled transaction"), Icon::DocumentEdit},
{Action::ScheduleDelete, &KMyMoneyApp::slotScheduleDelete, i18n("Delete scheduled transaction"), Icon::EditDelete},
{Action::ScheduleDuplicate, &KMyMoneyApp::slotScheduleDuplicate, i18n("Duplicate scheduled transaction"), Icon::EditCopy},
{Action::ScheduleEnter, &KMyMoneyApp::slotScheduleEnter, i18n("Enter next transaction..."), Icon::KeyEnter},
{Action::ScheduleSkip, &KMyMoneyApp::slotScheduleSkip, i18n("Skip next transaction..."), Icon::MediaSeekForward},
//Payees
{Action::PayeeNew, &KMyMoneyApp::slotPayeeNew, i18n("New payee"), Icon::ListAddUser},
{Action::PayeeRename, &KMyMoneyApp::payeeRename, i18n("Rename payee"), Icon::PayeeRename},
{Action::PayeeDelete, &KMyMoneyApp::slotPayeeDelete, i18n("Delete payee"), Icon::ListRemoveUser},
{Action::PayeeMerge, &KMyMoneyApp::slotPayeeMerge, i18n("Merge payees"), Icon::PayeeMerge},
//Tags
{Action::TagNew, &KMyMoneyApp::slotTagNew, i18n("New tag"), Icon::ListAddTag},
{Action::TagRename, &KMyMoneyApp::tagRename, i18n("Rename tag"), Icon::TagRename},
{Action::TagDelete, &KMyMoneyApp::slotTagDelete, i18n("Delete tag"), Icon::ListRemoveTag},
//Budget
{Action::BudgetNew, &KMyMoneyApp::slotBudgetNew, i18n("New budget"), Icon::Empty},
{Action::BudgetRename, &KMyMoneyApp::budgetRename, i18n("Rename budget"), Icon::Empty},
{Action::BudgetDelete, &KMyMoneyApp::slotBudgetDelete, i18n("Delete budget"), Icon::Empty},
{Action::BudgetCopy, &KMyMoneyApp::slotBudgetCopy, i18n("Copy budget"), Icon::Empty},
{Action::BudgetChangeYear, &KMyMoneyApp::slotBudgetChangeYear, i18n("Change budget year"), Icon::ViewCalendar},
{Action::BudgetForecast, &KMyMoneyApp::slotBudgetForecast, i18n("Budget based on forecast"), Icon::ViewForecast},
//Currency actions
{Action::CurrencyNew, &KMyMoneyApp::slotCurrencyNew, i18n("New currency"), Icon::Empty},
{Action::CurrencyRename, &KMyMoneyApp::currencyRename, i18n("Rename currency"), Icon::EditRename},
{Action::CurrencyDelete, &KMyMoneyApp::slotCurrencyDelete, i18n("Delete currency"), Icon::EditDelete},
{Action::CurrencySetBase, &KMyMoneyApp::slotCurrencySetBase, i18n("Select as base currency"), Icon::KMyMoney},
//Price actions
{Action::PriceNew, &KMyMoneyApp::priceNew, i18n("New price..."), Icon::DocumentNew},
{Action::PriceEdit, &KMyMoneyApp::priceEdit, i18n("Edit price..."), Icon::DocumentEdit},
{Action::PriceUpdate, &KMyMoneyApp::priceOnlineUpdate, i18n("Online Price Update..."), Icon::Empty},
{Action::PriceDelete, &KMyMoneyApp::priceDelete, i18n("Delete price..."), Icon::EditDelete},
//debug actions
#ifdef KMM_DEBUG
{Action::WizardNewUser, &KMyMoneyApp::slotNewFeature, i18n("Test new feature"), Icon::Empty},
{Action::DebugTraces, &KMyMoneyApp::slotToggleTraces, i18n("Debug Traces"), Icon::Empty},
#endif
{Action::DebugTimers, &KMyMoneyApp::slotToggleTimers, i18n("Debug Timers"), Icon::Empty},
// onlineJob actions
{Action::OnlineJobDelete, &KMyMoneyApp::slotRemoveJob, i18n("Remove credit transfer"), Icon::EditDelete},
{Action::OnlineJobEdit, &KMyMoneyApp::slotEditJob, i18n("Edit credit transfer"), Icon::DocumentEdit},
{Action::OnlineJobLog, &KMyMoneyApp::slotOnlineJobLog, i18n("Show log"), Icon::Empty},
};
foreach (const auto info, actionInfos) {
QAction *a = new QAction(0);
// KActionCollection::addAction by name sets object name anyways,
// so, as better alternative, set it here right from the start
a->setObjectName(s_Actions.value(info.action));
a->setText(info.text);
if (info.icon != Icon::Empty) // no need to set empty icon
a->setIcon(QIcon::fromTheme(g_Icons.value(info.icon)));
connect(a, &QAction::triggered, this, info.callback);
lutActions.insert(info.action, a); // store QAction's pointer for later processing
}
}
// *************
// Setting some of added actions checkable
// *************
{
// Some of acitions schould be checkable,
// so set them here
const QVector<Action> checkableActions {
Action::ViewTransactionDetail, Action::ViewHideReconciled, Action::ViewHideCategories,
#ifdef KMM_DEBUG
Action::DebugTraces,
#endif
Action::ViewShowAll
};
foreach (const auto it, checkableActions)
lutActions[it]->setCheckable(true);
}
// *************
// Setting custom icons for some of added actions
// *************
{
// struct for QAction's icon overlays
struct iconOverlayInfo {
Action action;
Icon icon;
Icon overlay;
Qt::Corner corner;
};
const QVector<iconOverlayInfo> actionOverlaidIcons {
{Action::EditFindTransaction, Icon::ViewFinancialTransfer, Icon::EditFind, Qt::BottomRightCorner},
{Action::InstitutionNew, Icon::ViewBank, Icon::ListAdd, Qt::BottomRightCorner},
{Action::InstitutionEdit, Icon::ViewBank, Icon::DocumentEdit, Qt::BottomRightCorner},
{Action::InstitutionDelete, Icon::ViewBank, Icon::EditDelete, Qt::BottomRightCorner},
{Action::AccountNew, Icon::ViewBankAccount, Icon::ListAdd, Qt::TopRightCorner},
{Action::AccountFinishReconciliation, Icon::Merge, Icon::DialogOK, Qt::BottomRightCorner},
{Action::AccountEdit, Icon::ViewBankAccount, Icon::DocumentEdit, Qt::BottomRightCorner},
{Action::AccountDelete, Icon::ViewBankAccount, Icon::EditDelete, Qt::BottomRightCorner},
{Action::AccountClose, Icon::ViewBankAccount, Icon::DialogClose, Qt::BottomRightCorner},
{Action::AccountReopen, Icon::ViewBankAccount, Icon::DialogOK, Qt::BottomRightCorner},
{Action::AccountUpdateMenu, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner},
{Action::AccountUpdate, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner},
{Action::AccountUpdateAll, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner},
{Action::AccountCreditTransfer, Icon::ViewBankAccount, Icon::MailMessageNew, Qt::BottomRightCorner},
{Action::CategoryNew, Icon::ViewFinancialCategories, Icon::ListAdd, Qt::TopRightCorner},
{Action::CategoryEdit, Icon::ViewFinancialCategories, Icon::DocumentEdit, Qt::BottomRightCorner},
{Action::CategoryDelete, Icon::ViewFinancialCategories, Icon::EditDelete, Qt::BottomRightCorner},
{Action::ToolUpdatePrices, Icon::ViewInvestment, Icon::Download, Qt::BottomRightCorner},
{Action::TransactionNew, Icon::ViewFinancialTransfer, Icon::ListAdd, Qt::TopRightCorner},
{Action::TransactionEdit, Icon::ViewFinancialTransfer, Icon::DocumentEdit, Qt::BottomRightCorner},
{Action::TransactionMatch, Icon::ViewFinancialTransfer, Icon::DocumentImport, Qt::BottomRightCorner},
{Action::TransactionAccept, Icon::ViewFinancialTransfer, Icon::DialogOKApply, Qt::BottomRightCorner},
{Action::InvestmentNew, Icon::ViewInvestment, Icon::ListAdd, Qt::TopRightCorner},
{Action::InvestmentEdit, Icon::ViewInvestment, Icon::DocumentEdit, Qt::BottomRightCorner},
{Action::InvestmentDelete, Icon::ViewInvestment, Icon::EditDelete, Qt::BottomRightCorner},
{Action::InvestmentOnlinePrice, Icon::ViewInvestment, Icon::Download, Qt::BottomRightCorner},
{Action::BudgetNew, Icon::ViewTimeScheduleCalculus, Icon::ListAdd, Qt::TopRightCorner},
{Action::BudgetRename, Icon::ViewTimeScheduleCalculus, Icon::DocumentEdit, Qt::BottomRightCorner},
{Action::BudgetDelete, Icon::ViewTimeScheduleCalculus, Icon::EditDelete, Qt::BottomRightCorner},
{Action::BudgetCopy, Icon::ViewTimeScheduleCalculus, Icon::EditCopy, Qt::BottomRightCorner},
{Action::PriceUpdate, Icon::ViewCurrencyList, Icon::Download, Qt::BottomRightCorner}
};
for(const auto& it : actionOverlaidIcons)
lutActions[it.action]->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[it.icon], g_Icons[it.overlay], it.corner));
}
// *************
// Setting keyboard shortcuts for some of added actions
// *************
{
const QVector<QPair<Action, QKeySequence>> 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::AccountStartReconciliation, Qt::CTRL + Qt::SHIFT + Qt::Key_R)},
{qMakePair(Action::TransactionNew, Qt::CTRL + Qt::Key_Insert)},
{qMakePair(Action::TransactionToggleReconciled, Qt::CTRL + Qt::Key_Space)},
{qMakePair(Action::TransactionToggleCleared, Qt::CTRL + Qt::Key_Alt + Qt::Key_Space)},
{qMakePair(Action::TransactionReconciled, Qt::CTRL + Qt::Key_Shift + Qt::Key_Space)},
{qMakePair(Action::TransactionSelectAll, Qt::CTRL + Qt::Key_A)},
#ifdef KMM_DEBUG
{qMakePair(Action::WizardNewUser, Qt::CTRL + Qt::Key_G)},
#endif
{qMakePair(Action::TransactionAssignNumber, 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
QAction *aboutApp = aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::AboutApp)));
aboutApp->disconnect();
connect(aboutApp, &QAction::triggered, this, &KMyMoneyApp::slotShowCredits);
QMenu *menuContainer;
menuContainer = static_cast<QMenu*>(factory()->container(QStringLiteral("import"), this));
menuContainer->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentImport]));
menuContainer = static_cast<QMenu*>(factory()->container(QStringLiteral("export"), this));
menuContainer->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentExport]));
}
void KMyMoneyApp::connectActionsAndViews()
{
KOnlineJobOutbox *const outbox = d->m_myMoneyView->getOnlineJobOutbox();
Q_CHECK_PTR(outbox);
QAction *const onlineJob_delete = actionCollection()->action(s_Actions[Action::OnlineJobDelete]);
Q_CHECK_PTR(onlineJob_delete);
connect(onlineJob_delete, SIGNAL(triggered()), outbox, SLOT(slotRemoveJob()));
QAction *const onlineJob_edit = actionCollection()->action(s_Actions[Action::OnlineJobEdit]);
Q_CHECK_PTR(onlineJob_edit);
connect(onlineJob_edit, SIGNAL(triggered()), outbox, SLOT(slotEditJob()));
}
#ifdef KMM_DEBUG
void KMyMoneyApp::dumpActions() const
{
const QList<QAction*> 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 actionCollection()->action(s_Actions[_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");
actionCollection()->action(s_Actions[Action::ViewHideReconciled])->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions());
actionCollection()->action(s_Actions[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
saveOptions();
return true;
}
/////////////////////////////////////////////////////////////////////
// SLOT IMPLEMENTATION
/////////////////////////////////////////////////////////////////////
void KMyMoneyApp::slotFileInfoDialog()
{
QPointer<KMyMoneyFileInfoDlg> 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<MyMoneyAccount> 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<MyMoneyTransaction> 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<MyMoneyTransaction> 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
MyMoneyAccount 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<MyMoneyTemplate> templates = wizard->templates();
QList<MyMoneyTemplate>::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<KRecentFilesAction*>(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);
}
}
QUrl KMyMoneyApp::selectFile(const QString& title, const QString& _path, const QString& mask, QFileDialog::FileMode mode, QWidget* widget)
{
QString path(_path);
// if the path is not specified open the file dialog in the last used directory
// 'kmymoney' is the keyword that identifies the last used directory in KFileDialog
if (path.isEmpty()) {
path = KRecentDirs::dir(":kmymoney-import");
}
QPointer<QFileDialog> dialog = new QFileDialog(this, title, path, mask);
dialog->setFileMode(mode);
QUrl url;
if (dialog->exec() == QDialog::Accepted && dialog != 0) {
QList<QUrl> selectedUrls = dialog->selectedUrls();
if (!selectedUrls.isEmpty()) {
url = selectedUrls.first();
if (_path.isEmpty()) {
KRecentDirs::add(":kmymoney-import", url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path());
}
}
}
// in case we have an additional widget, we remove it from the
// dialog, so that the caller can still access it. Therefore, it is
// the callers responsibility to delete the object
if (widget)
widget->setParent(0);
delete dialog;
return url;
}
// General open
void KMyMoneyApp::slotFileOpen()
{
KMSTATUS(i18n("Open a file."));
QString prevDir = readLastUsedDir();
QPointer<QFileDialog> 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<KSelectDatabaseDlg> 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<QString, KMyMoneyPlugin::ImporterPlugin*>::const_iterator it_plugin = d->m_importerPlugins.constBegin();
while (it_plugin != d->m_importerPlugins.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_importerPlugins.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<QString> list = instanceList();
QList<QString>::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<QString> 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<KSelectDatabaseDlg> 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;
}
// TODO: port KF5 (NetAccess)
if ((newurl.scheme() == QLatin1String("sql")) || (newurl.isValid() /*&& KIO::NetAccess::exists(newurl, KIO::NetAccess::SourceSide, this)*/)) {
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("<p><b>%1</b> is either an invalid filename or the file does not exist. You can open another file or create a new one.</p>", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File not found"));
}
} else { // isDuplicate
KMessageBox::sorry(this, i18n("<p>File <b>%1</b> is already opened in another instance of KMyMoney</p>", 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"));
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<KGpgKeySelectionDlg> 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<IMyMoneySerialize*>(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
QString 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<QFileDialog> 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<IMyMoneySerialize*>(MyMoneyFile::instance()->storage())->fillStorage();
oldUrl = d->m_fileName.isEmpty() ? lastOpenedURL() : d->m_fileName;
}
KMSTATUS(i18n("Saving file to database..."));
QPointer<KSelectDatabaseDlg> 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<KRecentFilesAction*>(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<KMainWindow*> memberList = KMainWindow::memberList();
if (!memberList.isEmpty()) {
QList<KMainWindow*>::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(actionCollection()->action(s_Actions[Action::ViewHideReconciled])->isChecked());
d->m_myMoneyView->slotRefreshViews();
}
void KMyMoneyApp::slotHideUnusedCategories()
{
KMyMoneyGlobalSettings::setHideUnusedCategory(actionCollection()->action(s_Actions[Action::ViewHideCategories])->isChecked());
d->m_myMoneyView->slotRefreshViews();
}
void KMyMoneyApp::slotShowAllAccounts()
{
d->m_myMoneyView->slotRefreshViews();
}
#ifdef KMM_DEBUG
void KMyMoneyApp::slotToggleTraces()
{
MyMoneyTracer::onOff(actionCollection()->action(s_Actions[Action::DebugTraces])->isChecked() ? 1 : 0);
}
#endif
void KMyMoneyApp::slotToggleTimers()
{
extern bool timersOn; // main.cpp
timersOn = actionCollection()->action(s_Actions[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> 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->userNameText);
user.setAddress(editPersonalDataDlg->userStreetText);
user.setCity(editPersonalDataDlg->userTownText);
user.setState(editPersonalDataDlg->userCountyText);
user.setPostcode(editPersonalDataDlg->userPostcodeText);
user.setTelephone(editPersonalDataDlg->userTelephoneText);
user.setEmail(editPersonalDataDlg->userEmailText);
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<IMyMoneySerialize*>(MyMoneyFile::instance()->storage()));
g.close();
}
void KMyMoneyApp::slotLoadAccountTemplates()
{
KMSTATUS(i18n("Importing account templates."));
int rc;
QPointer<KLoadTemplateDlg> dlg = new KLoadTemplateDlg();
if ((rc = dlg->exec()) == QDialog::Accepted && dlg != 0) {
MyMoneyFileTransaction ft;
try {
// import the account templates
QList<MyMoneyTemplate> templates = dlg->templates();
QList<MyMoneyTemplate>::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 <KTemplateExportDlg> 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;
}
}
}
void KMyMoneyApp::slotGncImport()
{
if (d->m_myMoneyView->fileOpen()) {
switch (KMessageBox::questionYesNoCancel(0,
i18n("You cannot import GnuCash data into an existing file. Do you wish to save this file?"), PACKAGE)) {
case KMessageBox::Yes:
slotFileSave();
break;
case KMessageBox::No:
d->closeFile();
break;
default:
return;
}
}
KMSTATUS(i18n("Importing a GnuCash file."));
QUrl fileToRead = QFileDialog::getOpenFileUrl(this, QString(), QUrl(), i18n("GnuCash files (*.gnucash *.xac *.gnc);;All files (*)"));
if (!fileToRead.isEmpty()) {
// call the importer
if (d->m_myMoneyView->readFile(fileToRead)) {
// imported files don't have a name
d->m_fileName = QUrl();
updateCaption();
emit fileLoaded(d->m_fileName);
}
}
}
void KMyMoneyApp::slotAccountChart()
{
if (!d->m_selectedAccount.id().isEmpty()) {
QPointer<KBalanceChartDlg> dlg = new KBalanceChartDlg(d->m_selectedAccount, this);
dlg->exec();
delete dlg;
}
}
//
// KMyMoneyApp::slotStatementImport() is for testing only. The MyMoneyStatement
// is not intended to be exposed to users in XML form.
//
void KMyMoneyApp::slotStatementImport()
{
bool result = false;
KMSTATUS(i18n("Importing an XML Statement."));
QList<QUrl> files{QFileDialog::getOpenFileUrls(this, QString(), QUrl(),
i18n("XML files (*.xml);;All files (*)"))};
if (!files.isEmpty()) {
d->m_collectingStatements = (files.count() > 1);
foreach (const QUrl &url, files) {
qDebug("Processing '%s'", qPrintable(url.path()));
result |= slotStatementImport(url.path());
}
/* QFile f( dialog->selectedURL().path() );
f.open( QIODevice::ReadOnly );
QString error = "Unable to parse file";
QDomDocument* doc = new QDomDocument;
if(doc->setContent(&f, FALSE))
{
if ( doc->doctype().name() == "KMYMONEY-STATEMENT" )
{
QDomElement rootElement = doc->documentElement();
if(!rootElement.isNull())
{
QDomNode child = rootElement.firstChild();
if(!child.isNull() && child.isElement())
{
MyMoneyStatement s;
if ( s.read(child.toElement()) )
result = slotStatementImport(s);
else
error = "File does not contain any statements";
}
}
}
else
error = "File is not a KMyMoney Statement";
}
delete doc;
if ( !result )
{
QMessageBox::critical( this, i18n("Critical Error"), i18n("Unable to read file %1: %2").arg( dialog->selectedURL().path()).arg(error), QMessageBox::Ok, 0 );
}*/
}
if (!result) {
// re-enable all standard widgets
setEnabled(true);
}
}
bool KMyMoneyApp::slotStatementImport(const QString& url)
{
bool result = false;
MyMoneyStatement s;
if (MyMoneyStatement::readXMLFile(s, url))
result = slotStatementImport(s);
else
KMessageBox::error(this, i18n("Error importing %1: This file is not a valid KMM statement file.", url), i18n("Invalid Statement"));
return result;
}
bool KMyMoneyApp::slotStatementImport(const MyMoneyStatement& s, bool silent)
{
bool result = false;
// keep a copy of the statement
if (KMyMoneySettings::logImportedStatements()) {
QString logFile = QString("%1/kmm-statement-%2.txt").arg(KMyMoneySettings::logPath()).arg(d->m_statementXMLindex++);
MyMoneyStatement::writeXMLFile(s, logFile);
}
// we use an object on the heap here, so that we can check the presence
// of it during slotUpdateActions() by looking at the pointer.
d->m_smtReader = new MyMoneyStatementReader;
d->m_smtReader->setAutoCreatePayee(true);
d->m_smtReader->setProgressCallback(&progressCallback);
// disable all standard widgets during the import
setEnabled(false);
QStringList messages;
result = d->m_smtReader->import(s, messages);
bool transactionAdded = d->m_smtReader->anyTransactionAdded();
// get rid of the statement reader and tell everyone else
// about the destruction by setting the pointer to zero
delete d->m_smtReader;
d->m_smtReader = 0;
slotStatusProgressBar(-1, -1);
ready();
// re-enable all standard widgets
setEnabled(true);
if (!d->m_collectingStatements && !silent)
KMessageBox::informationList(this, i18n("The statement has been processed with the following results:"), messages, i18n("Statement stats"));
else if (transactionAdded)
d->m_statementResults += messages;
slotUpdateActions();// Re-enable menu items after import via plugin.
return result;
}
bool KMyMoneyApp::okToWriteFile(const QUrl &url)
{
Q_UNUSED(url)
// check if the file exists and warn the user
bool reallySaveFile = true;
// TODO: port KF5 (NetAccess)
//if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, this)) {
// if (KMessageBox::warningYesNo(this, QLatin1String("<qt>") + i18n("The file <b>%1</b> already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)) + QLatin1String("</qt>"), 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
KConfigDialog* dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneyGlobalSettings::self());
connect(dlg, &KConfigDialog::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration);
dlg->show();
}
void KMyMoneyApp::slotShowCredits()
{
KAboutData aboutData = initializeCreditsData();
KAboutApplicationDialog dlg(aboutData, this);
dlg.exec();
}
void KMyMoneyApp::slotUpdateConfiguration()
{
MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay());
LedgerSeperator::setFirstFiscalDate(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay());
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<KBackupDlg> backupDlg = new KBackupDlg(this);
#ifdef Q_OS_WIN
backupDlg->mountCheckBox->setEnabled(false);
#endif
int returncode = backupDlg->exec();
if (returncode == QDialog::Accepted && backupDlg != 0) {
d->m_backupMount = backupDlg->mountCheckBox->isChecked();
d->m_proc.clearProgram();
d->m_backupState = BACKUP_MOUNTING;
d->m_mountpoint = backupDlg->txtMountPoint->text();
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<KGenerateSqlDlg> 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::createInstitution(MyMoneyInstitution& institution)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyFileTransaction ft;
try {
file->addInstitution(institution);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::information(this, i18n("Cannot add institution: %1", e.what()));
}
}
void KMyMoneyApp::slotInstitutionNew()
{
MyMoneyInstitution institution;
slotInstitutionNew(institution);
}
void KMyMoneyApp::slotInstitutionNew(MyMoneyInstitution& institution)
{
institution.clearId();
QPointer<KNewBankDlg> dlg = new KNewBankDlg(institution);
if (dlg->exec() == QDialog::Accepted && dlg != 0) {
institution = dlg->institution();
createInstitution(institution);
}
delete dlg;
}
void KMyMoneyApp::slotInstitutionEditEmpty()
{
slotInstitutionEdit(MyMoneyInstitution());
}
void KMyMoneyApp::slotInstitutionEdit(const MyMoneyObject& obj)
{
if (typeid(obj) != typeid(MyMoneyInstitution))
return;
// make sure the selected object has an id
if (d->m_selectedInstitution.id().isEmpty())
return;
try {
MyMoneyFile* file = MyMoneyFile::instance();
//grab a pointer to the view, regardless of it being a account or institution view.
MyMoneyInstitution institution = file->institution(d->m_selectedInstitution.id());
// bankSuccess is not checked anymore because d->m_file->institution will throw anyway
QPointer<KNewBankDlg> dlg = new KNewBankDlg(institution);
if (dlg->exec() == QDialog::Accepted && dlg != 0) {
MyMoneyFileTransaction ft;
try {
file->modifyInstitution(dlg->institution());
ft.commit();
slotSelectInstitution(file->institution(dlg->institution().id()));
} catch (const MyMoneyException &e) {
KMessageBox::information(this, i18n("Unable to store institution: %1", e.what()));
}
}
delete dlg;
} catch (const MyMoneyException &e) {
if (!obj.id().isEmpty())
KMessageBox::information(this, i18n("Unable to edit institution: %1", e.what()));
}
}
void KMyMoneyApp::slotInstitutionDelete()
{
MyMoneyFile *file = MyMoneyFile::instance();
try {
MyMoneyInstitution institution = file->institution(d->m_selectedInstitution.id());
if ((KMessageBox::questionYesNo(this, i18n("<p>Do you really want to delete the institution <b>%1</b>?</p>", institution.name()))) == KMessageBox::No)
return;
MyMoneyFileTransaction ft;
try {
file->removeInstitution(institution);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::information(this, i18n("Unable to delete institution: %1", e.what()));
}
} catch (const MyMoneyException &e) {
KMessageBox::information(this, i18n("Unable to delete institution: %1", e.what()));
}
}
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() == MyMoneyAccount::Liability) {
+ if (openingBal.isPositive() && newAccount.accountGroup() == eMyMoney::Account::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::slotCategoryNew(const QString& name, QString& id)
{
MyMoneyAccount account;
account.setName(name);
slotCategoryNew(account, MyMoneyFile::instance()->expense());
id = account.id();
}
void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent)
{
if (KMessageBox::questionYesNo(this,
QString("<qt>%1</qt>").arg(i18n("<p>The category <b>%1</b> currently does not exist. Do you want to create it?</p><p><i>The parent account will default to <b>%2</b> but can be changed in the following dialog</i>.</p>", account.name(), parent.name())), i18n("Create category"),
KStandardGuiItem::yes(), KStandardGuiItem::no(), "CreateNewCategories") == KMessageBox::Yes) {
createCategory(account, parent);
} else {
// we should not keep the 'no' setting because that can confuse people like
// I have seen in some usability tests. So we just delete it right away.
KSharedConfigPtr kconfig = KSharedConfig::openConfig();
if (kconfig) {
kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("CreateNewCategories"));
}
}
}
void KMyMoneyApp::slotCategoryNew()
{
MyMoneyAccount parent;
MyMoneyAccount account;
// Preselect the parent account by looking at the current selected account/category
if (!d->m_selectedAccount.id().isEmpty() && d->m_selectedAccount.isIncomeExpense()) {
MyMoneyFile* file = MyMoneyFile::instance();
try {
parent = file->account(d->m_selectedAccount.id());
} catch (const MyMoneyException &) {
}
}
createCategory(account, parent);
}
void KMyMoneyApp::createCategory(MyMoneyAccount& account, const MyMoneyAccount& parent)
{
if (!parent.id().isEmpty()) {
try {
// make sure parent account exists
MyMoneyFile::instance()->account(parent.id());
account.setParentAccountId(parent.id());
account.setAccountType(parent.accountType());
} catch (const MyMoneyException &) {
}
}
QPointer<KNewAccountDlg> dialog =
new KNewAccountDlg(account, false, true, 0, i18n("Create a new Category"));
dialog->setOpeningBalanceShown(false);
dialog->setOpeningDateShown(false);
if (dialog->exec() == QDialog::Accepted && dialog != 0) {
MyMoneyAccount parentAccount, brokerageAccount;
account = dialog->account();
parentAccount = dialog->parentAccount();
MyMoneyFile::instance()->createAccount(account, parentAccount, brokerageAccount, MyMoneyMoney());
}
delete dialog;
}
void KMyMoneyApp::slotAccountNew()
{
MyMoneyAccount acc;
acc.setInstitutionId(d->m_selectedInstitution.id());
acc.setOpeningDate(KMyMoneyGlobalSettings::firstFiscalDate());
slotAccountNew(acc);
}
void KMyMoneyApp::slotAccountNew(MyMoneyAccount& account)
{
NewAccountWizard::Wizard* wizard = new NewAccountWizard::Wizard();
connect(wizard, SIGNAL(createInstitution(MyMoneyInstitution&)), this, SLOT(slotInstitutionNew(MyMoneyInstitution&)));
connect(wizard, SIGNAL(createAccount(MyMoneyAccount&)), this, SLOT(slotAccountNew(MyMoneyAccount&)));
connect(wizard, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&)));
connect(wizard, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), this, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount)));
wizard->setAccount(account);
if (wizard->exec() == QDialog::Accepted) {
MyMoneyAccount acc = wizard->account();
if (!(acc == MyMoneyAccount())) {
MyMoneyFileTransaction ft;
MyMoneyFile* file = MyMoneyFile::instance();
try {
// create the account
MyMoneyAccount parent = wizard->parentAccount();
file->addAccount(acc, parent);
// tell the wizard about the account id which it
// needs to create a possible schedule and transactions
wizard->setAccount(acc);
// store a possible conversion rate for the currency
if (acc.currencyId() != file->baseCurrency().id()) {
file->addPrice(wizard->conversionRate());
}
// create the opening balance transaction if any
file->createOpeningBalanceTransaction(acc, wizard->openingBalance());
// create the payout transaction for loans if any
MyMoneyTransaction payoutTransaction = wizard->payoutTransaction();
if (payoutTransaction.splits().count() > 0) {
file->addTransaction(payoutTransaction);
}
// create a brokerage account if selected
MyMoneyAccount brokerageAccount = wizard->brokerageAccount();
if (!(brokerageAccount == MyMoneyAccount())) {
file->addAccount(brokerageAccount, parent);
}
// create a possible schedule
MyMoneySchedule sch = wizard->schedule();
if (!(sch == MyMoneySchedule())) {
MyMoneyFile::instance()->addSchedule(sch);
if (acc.isLoan()) {
MyMoneyAccountLoan accLoan = MyMoneyFile::instance()->account(acc.id());
accLoan.setSchedule(sch.id());
acc = accLoan;
MyMoneyFile::instance()->modifyAccount(acc);
}
}
ft.commit();
account = acc;
} catch (const MyMoneyException &e) {
KMessageBox::error(this, i18n("Unable to create account: %1", e.what()));
}
}
}
delete wizard;
}
void KMyMoneyApp::slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent)
{
QString dontShowAgain = "CreateNewInvestments";
if (KMessageBox::questionYesNo(this,
QString("<qt>") + i18n("The security <b>%1</b> currently does not exist as sub-account of <b>%2</b>. "
"Do you want to create it?", account.name(), parent.name()) + QString("</qt>"), i18n("Create security"),
KStandardGuiItem::yes(), KStandardGuiItem::no(), dontShowAgain) == KMessageBox::Yes) {
KNewInvestmentWizard dlg;
dlg.setName(account.name());
if (dlg.exec() == QDialog::Accepted) {
dlg.createObjects(parent.id());
account = dlg.account();
}
} else {
// in case the user said no but turned on the don't show again selection, we will enable
// the message no matter what. Otherwise, the user is not able to use this feature
// in the future anymore.
KMessageBox::enableMessage(dontShowAgain);
}
}
void KMyMoneyApp::slotInvestmentNew()
{
QPointer<KNewInvestmentWizard> dlg = new KNewInvestmentWizard(this);
if (dlg->exec() == QDialog::Accepted)
dlg->createObjects(d->m_selectedAccount.id());
delete dlg;
}
void KMyMoneyApp::slotInvestmentEdit()
{
QPointer<KNewInvestmentWizard> dlg = new KNewInvestmentWizard(d->m_selectedInvestment);
if (dlg->exec() == QDialog::Accepted)
dlg->createObjects(d->m_selectedAccount.id());
delete dlg;
}
void KMyMoneyApp::slotInvestmentDelete()
{
if (KMessageBox::questionYesNo(this, i18n("<p>Do you really want to delete the investment <b>%1</b>?</p>", d->m_selectedInvestment.name()), i18n("Delete investment"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "DeleteInvestment") == KMessageBox::Yes) {
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyFileTransaction ft;
try {
d->m_selectedAccount = MyMoneyAccount(); // CAUTION: deleting equity from investments view needs this, if ID of the equity to be deleted is the smallest from all
file->removeAccount(d->m_selectedInvestment);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::information(this, i18n("Unable to delete investment: %1", e.what()));
}
} else {
// we should not keep the 'no' setting because that can confuse people like
// I have seen in some usability tests. So we just delete it right away.
KSharedConfigPtr kconfig = KSharedConfig::openConfig();
if (kconfig) {
kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("DeleteInvestment"));
}
}
}
void KMyMoneyApp::slotOnlinePriceUpdate()
{
if (!d->m_selectedInvestment.id().isEmpty()) {
QPointer<KEquityPriceUpdateDlg> dlg =
new KEquityPriceUpdateDlg(0, d->m_selectedInvestment.currencyId());
if (dlg->exec() == QDialog::Accepted && dlg != 0) {
dlg->storePrices();
}
delete dlg;
}
}
void KMyMoneyApp::slotManualPriceUpdate()
{
if (!d->m_selectedInvestment.id().isEmpty()) {
try {
MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedInvestment.currencyId());
MyMoneySecurity currency = MyMoneyFile::instance()->security(security.tradingCurrency());
const MyMoneyPrice &price = MyMoneyFile::instance()->price(security.id(), currency.id());
QPointer<KCurrencyCalculator> calc =
new KCurrencyCalculator(security, currency, MyMoneyMoney::ONE, price.rate(currency.id()), price.date(), MyMoneyMoney::precToDenom(security.pricePrecision()));
calc->setupPriceEditor();
// The dialog takes care of adding the price if necessary
calc->exec();
delete calc;
} catch (const MyMoneyException &e) {
qDebug("Error in price update: %s", qPrintable(e.what()));
}
}
}
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() == MyMoneyAccount::Loan
- || newAccount.accountType() == MyMoneyAccount::AssetLoan) {
+ if (newAccount.accountType() == eMyMoney::Account::Loan
+ || newAccount.accountType() == eMyMoney::Account::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()));
}
}
}
void KMyMoneyApp::slotAccountDelete()
{
if (d->m_selectedAccount.id().isEmpty())
return; // need an account ID
MyMoneyFile* file = MyMoneyFile::instance();
// can't delete standard accounts or account which still have transactions assigned
if (file->isStandardAccount(d->m_selectedAccount.id()))
return;
// check if the account is referenced by a transaction or schedule
QBitArray skip((int)eStorage::Reference::Count);
skip.fill(false);
skip.setBit((int)eStorage::Reference::Account);
skip.setBit((int)eStorage::Reference::Institution);
skip.setBit((int)eStorage::Reference::Payee);
skip.setBit((int)eStorage::Reference::Tag);
skip.setBit((int)eStorage::Reference::Security);
skip.setBit((int)eStorage::Reference::Currency);
skip.setBit((int)eStorage::Reference::Price);
bool hasReference = file->isReferenced(d->m_selectedAccount, skip);
// make sure we only allow transactions in a 'category' (income/expense account)
switch (d->m_selectedAccount.accountType()) {
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
+ case eMyMoney::Account::Income:
+ case eMyMoney::Account::Expense:
break;
default:
// if the account is still referenced
if (hasReference) {
return;
}
break;
}
// if we get here and still have transactions referencing the account, we
// need to check with the user to possibly re-assign them to a different account
bool needAskUser = true;
bool exit = false;
MyMoneyFileTransaction ft;
if (hasReference) {
// show transaction reassignment dialog
needAskUser = false;
KCategoryReassignDlg* dlg = new KCategoryReassignDlg(this);
QString categoryId = dlg->show(d->m_selectedAccount);
delete dlg; // and kill the dialog
if (categoryId.isEmpty())
return; // the user aborted the dialog, so let's abort as well
MyMoneyAccount newCategory = file->account(categoryId);
try {
{
KMSTATUS(i18n("Adjusting transactions..."));
/*
d->m_selectedAccount.id() is the old id, categoryId the new one
Now search all transactions and schedules that reference d->m_selectedAccount.id()
and replace that with categoryId.
*/
// get the list of all transactions that reference the old account
MyMoneyTransactionFilter filter(d->m_selectedAccount.id());
filter.setReportAllSplits(false);
QList<MyMoneyTransaction> tlist;
QList<MyMoneyTransaction>::iterator it_t;
file->transactionList(tlist, filter);
slotStatusProgressBar(0, tlist.count());
int cnt = 0;
for (it_t = tlist.begin(); it_t != tlist.end(); ++it_t) {
slotStatusProgressBar(++cnt, 0);
MyMoneyTransaction t = (*it_t);
if (t.replaceId(categoryId, d->m_selectedAccount.id()))
file->modifyTransaction(t);
}
slotStatusProgressBar(tlist.count(), 0);
}
// now fix all schedules
{
KMSTATUS(i18n("Adjusting scheduled transactions..."));
QList<MyMoneySchedule> slist = file->scheduleList(d->m_selectedAccount.id());
QList<MyMoneySchedule>::iterator it_s;
int cnt = 0;
slotStatusProgressBar(0, slist.count());
for (it_s = slist.begin(); it_s != slist.end(); ++it_s) {
slotStatusProgressBar(++cnt, 0);
MyMoneySchedule sch = (*it_s);
if (sch.replaceId(categoryId, d->m_selectedAccount.id())) {
file->modifySchedule(sch);
}
}
slotStatusProgressBar(slist.count(), 0);
}
// now fix all budgets
{
KMSTATUS(i18n("Adjusting budgets..."));
QList<MyMoneyBudget> blist = file->budgetList();
QList<MyMoneyBudget>::const_iterator it_b;
for (it_b = blist.constBegin(); it_b != blist.constEnd(); ++it_b) {
if ((*it_b).hasReferenceTo(d->m_selectedAccount.id())) {
MyMoneyBudget b = (*it_b);
MyMoneyBudget::AccountGroup fromBudget = b.account(d->m_selectedAccount.id());
MyMoneyBudget::AccountGroup toBudget = b.account(categoryId);
toBudget += fromBudget;
b.setAccount(toBudget, categoryId);
b.removeReference(d->m_selectedAccount.id());
file->modifyBudget(b);
}
}
slotStatusProgressBar(blist.count(), 0);
}
} catch (MyMoneyException &e) {
KMessageBox::error(this, i18n("Unable to exchange category <b>%1</b> with category <b>%2</b>. Reason: %3", d->m_selectedAccount.name(), newCategory.name(), e.what()));
exit = true;
}
slotStatusProgressBar(-1, -1);
}
if (exit)
return;
// retain the account name for a possible later usage in the error message box
// since the account removal notifies the views the selected account can be changed
// so we make sure by doing this that we display the correct name in the error message
QString selectedAccountName = d->m_selectedAccount.name();
// at this point, we must not have a reference to the account
// to be deleted anymore
switch (d->m_selectedAccount.accountGroup()) {
// special handling for categories to allow deleting of empty subcategories
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense: { // open a compound statement here to be able to declare variables
+ case eMyMoney::Account::Income:
+ case eMyMoney::Account::Expense: { // open a compound statement here to be able to declare variables
// which would otherwise not work within a case label.
// case A - only a single, unused category without subcats selected
if (d->m_selectedAccount.accountList().isEmpty()) {
if (!needAskUser || (KMessageBox::questionYesNo(this, QString("<qt>") + i18n("Do you really want to delete category <b>%1</b>?", selectedAccountName) + QString("</qt>")) == KMessageBox::Yes)) {
try {
file->removeAccount(d->m_selectedAccount);
d->m_selectedAccount.clearId();
slotUpdateActions();
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::error(this, QString("<qt>") + i18n("Unable to delete category <b>%1</b>. Cause: %2", selectedAccountName, e.what()) + QString("</qt>"));
}
}
return;
}
// case B - we have some subcategories, maybe the user does not want to
// delete them all, but just the category itself?
MyMoneyAccount parentAccount = file->account(d->m_selectedAccount.parentAccountId());
QStringList accountsToReparent;
int result = KMessageBox::questionYesNoCancel(this, QString("<qt>") +
i18n("Do you want to delete category <b>%1</b> with all its sub-categories or only "
"the category itself? If you only delete the category itself, all its sub-categories "
"will be made sub-categories of <b>%2</b>.", selectedAccountName, parentAccount.name()) + QString("</qt>"),
QString(),
KGuiItem(i18n("Delete all")),
KGuiItem(i18n("Just the category")));
if (result == KMessageBox::Cancel)
return; // cancel pressed? ok, no delete then...
// "No" means "Just the category" and that means we need to reparent all subaccounts
bool need_confirmation = false;
// case C - User only wants to delete the category itself
if (result == KMessageBox::No)
accountsToReparent = d->m_selectedAccount.accountList();
else {
// case D - User wants to delete all subcategories, now check all subcats of
// d->m_selectedAccount and remember all that cannot be deleted and
// must be "reparented"
for (QStringList::const_iterator it = d->m_selectedAccount.accountList().begin();
it != d->m_selectedAccount.accountList().end(); ++it) {
// reparent account if a transaction is assigned
if (file->transactionCount(*it) != 0)
accountsToReparent.push_back(*it);
else if (!file->account(*it).accountList().isEmpty()) {
// or if we have at least one sub-account that is used for transactions
if (!file->hasOnlyUnusedAccounts(file->account(*it).accountList())) {
accountsToReparent.push_back(*it);
//qDebug() << "subaccount not empty";
}
}
}
if (!accountsToReparent.isEmpty())
need_confirmation = true;
}
if (!accountsToReparent.isEmpty() && need_confirmation) {
if (KMessageBox::questionYesNo(this, i18n("<p>Some sub-categories of category <b>%1</b> cannot "
"be deleted, because they are still used. They will be made sub-categories of <b>%2</b>. Proceed?</p>", selectedAccountName, parentAccount.name())) != KMessageBox::Yes) {
return; // user gets wet feet...
}
}
// all good, now first reparent selected sub-categories
try {
MyMoneyAccount parent = file->account(d->m_selectedAccount.parentAccountId());
for (QStringList::const_iterator it = accountsToReparent.constBegin(); it != accountsToReparent.constEnd(); ++it) {
MyMoneyAccount child = file->account(*it);
file->reparentAccount(child, parent);
}
// reload the account because the sub-account list might have changed
d->m_selectedAccount = file->account(d->m_selectedAccount.id());
// now recursively delete remaining sub-categories
file->removeAccountList(d->m_selectedAccount.accountList());
// don't forget to update d->m_selectedAccount, because we still have a copy of
// the old account list, which is no longer valid
d->m_selectedAccount = file->account(d->m_selectedAccount.id());
} catch (const MyMoneyException &e) {
KMessageBox::error(this, QString("<qt>") + i18n("Unable to delete a sub-category of category <b>%1</b>. Reason: %2", selectedAccountName, e.what()) + QString("</qt>"));
return;
}
}
break; // the category/account is deleted after the switch
default:
if (!d->m_selectedAccount.accountList().isEmpty())
return; // can't delete accounts which still have subaccounts
if (KMessageBox::questionYesNo(this, i18n("<p>Do you really want to "
"delete account <b>%1</b>?</p>", selectedAccountName)) != KMessageBox::Yes) {
return; // ok, you don't want to? why did you click then, hmm?
}
} // switch;
try {
file->removeAccount(d->m_selectedAccount);
d->m_selectedAccount.clearId();
slotUpdateActions();
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::error(this, i18n("Unable to delete account '%1'. Cause: %2", selectedAccountName, e.what()));
}
}
void KMyMoneyApp::slotAccountEdit()
{
MyMoneyFile* file = MyMoneyFile::instance();
if (!d->m_selectedAccount.id().isEmpty()) {
if (!file->isStandardAccount(d->m_selectedAccount.id())) {
- if (d->m_selectedAccount.accountType() != MyMoneyAccount::Loan
- && d->m_selectedAccount.accountType() != MyMoneyAccount::AssetLoan) {
+ if (d->m_selectedAccount.accountType() != eMyMoney::Account::Loan
+ && d->m_selectedAccount.accountType() != eMyMoney::Account::AssetLoan) {
QString caption;
bool category = false;
switch (d->m_selectedAccount.accountGroup()) {
default:
caption = i18n("Edit account '%1'", d->m_selectedAccount.name());
break;
- case MyMoneyAccount::Expense:
- case MyMoneyAccount::Income:
+ case eMyMoney::Account::Expense:
+ case eMyMoney::Account::Income:
caption = i18n("Edit category '%1'", d->m_selectedAccount.name());
category = true;
break;
}
// set a status message so that the application can't be closed until the editing is done
slotStatusMsg(caption);
QString tid = file->openingBalanceTransaction(d->m_selectedAccount);
MyMoneyTransaction t;
MyMoneySplit s0, s1;
QPointer<KNewAccountDlg> dlg =
new KNewAccountDlg(d->m_selectedAccount, true, category, 0, caption);
if (category) {
dlg->setOpeningBalanceShown(false);
dlg->setOpeningDateShown(false);
tid.clear();
} else {
if (!tid.isEmpty()) {
try {
t = file->transaction(tid);
s0 = t.splitByAccount(d->m_selectedAccount.id());
s1 = t.splitByAccount(d->m_selectedAccount.id(), false);
dlg->setOpeningBalance(s0.shares());
- if (d->m_selectedAccount.accountGroup() == MyMoneyAccount::Liability) {
+ if (d->m_selectedAccount.accountGroup() == eMyMoney::Account::Liability) {
dlg->setOpeningBalance(-s0.shares());
}
} catch (const MyMoneyException &e) {
qDebug() << "Error retrieving opening balance transaction " << tid << ": " << e.what() << "\n";
tid.clear();
}
}
}
// check for online modules
QMap<QString, KMyMoneyPlugin::OnlinePlugin *>::const_iterator it_plugin = d->m_onlinePlugins.constEnd();
const MyMoneyKeyValueContainer& kvp = d->m_selectedAccount.onlineBankingSettings();
if (!kvp["provider"].isEmpty()) {
// if we have an online provider for this account, we need to check
// that we have the corresponding plugin. If that exists, we ask it
// to provide an additional tab for the account editor.
it_plugin = d->m_onlinePlugins.constFind(kvp["provider"]);
if (it_plugin != d->m_onlinePlugins.constEnd()) {
QString name;
QWidget *w = (*it_plugin)->accountConfigTab(d->m_selectedAccount, name);
dlg->addTab(w, name);
}
}
if (dlg != 0 && dlg->exec() == QDialog::Accepted) {
try {
MyMoneyFileTransaction ft;
MyMoneyAccount account = dlg->account();
MyMoneyAccount parent = dlg->parentAccount();
if (it_plugin != d->m_onlinePlugins.constEnd()) {
account.setOnlineBankingSettings((*it_plugin)->onlineBankingSettings(account.onlineBankingSettings()));
}
MyMoneyMoney bal = dlg->openingBalance();
- if (d->m_selectedAccount.accountGroup() == MyMoneyAccount::Liability) {
+ if (d->m_selectedAccount.accountGroup() == eMyMoney::Account::Liability) {
bal = -bal;
}
// we need to modify first, as reparent would override all other changes
file->modifyAccount(account);
if (account.parentAccountId() != parent.id()) {
file->reparentAccount(account, parent);
}
if (!tid.isEmpty() && dlg->openingBalance().isZero()) {
file->removeTransaction(t);
} else if (!tid.isEmpty() && !dlg->openingBalance().isZero()) {
s0.setShares(bal);
s0.setValue(bal);
t.modifySplit(s0);
s1.setShares(-bal);
s1.setValue(-bal);
t.modifySplit(s1);
t.setPostDate(account.openingDate());
file->modifyTransaction(t);
} else if (tid.isEmpty() && !dlg->openingBalance().isZero()) {
file->createOpeningBalanceTransaction(d->m_selectedAccount, bal);
}
ft.commit();
// reload the account object as it might have changed in the meantime
slotSelectAccount(file->account(account.id()));
} catch (const MyMoneyException &e) {
KMessageBox::error(this, i18n("Unable to modify account '%1'. Cause: %2", d->m_selectedAccount.name(), e.what()));
}
}
delete dlg;
ready();
} else {
QPointer<KEditLoanWizard> wizard = new KEditLoanWizard(d->m_selectedAccount);
connect(wizard, SIGNAL(newCategory(MyMoneyAccount&)), this, SLOT(slotCategoryNew(MyMoneyAccount&)));
connect(wizard, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&)));
if (wizard->exec() == QDialog::Accepted && wizard != 0) {
MyMoneySchedule sch;
try {
MyMoneySchedule sch = file->schedule(d->m_selectedAccount.value("schedule").toLatin1());
} catch (const MyMoneyException &e) {
qDebug() << "schedule" << d->m_selectedAccount.value("schedule").toLatin1() << "not found";
}
if (!(d->m_selectedAccount == wizard->account())
|| !(sch == wizard->schedule())) {
MyMoneyFileTransaction ft;
try {
file->modifyAccount(wizard->account());
if (!sch.id().isEmpty()) {
sch = wizard->schedule();
}
try {
file->schedule(sch.id());
file->modifySchedule(sch);
ft.commit();
} catch (const MyMoneyException &) {
try {
if(sch.transaction().splitCount() >= 2) {
file->addSchedule(sch);
}
ft.commit();
} catch (const MyMoneyException &e) {
qDebug("Cannot add schedule: '%s'", qPrintable(e.what()));
}
}
} catch (const MyMoneyException &e) {
qDebug("Unable to modify account %s: '%s'", qPrintable(d->m_selectedAccount.name()),
qPrintable(e.what()));
}
}
}
delete wizard;
}
}
}
}
QList<QPair<MyMoneyTransaction, MyMoneySplit> > KMyMoneyApp::Private::automaticReconciliation(const MyMoneyAccount &account,
const QList<QPair<MyMoneyTransaction, MyMoneySplit> > &transactions,
const MyMoneyMoney &amount)
{
static const int NR_OF_STEPS_LIMIT = 300000;
static const int PROGRESSBAR_STEPS = 1000;
QList<QPair<MyMoneyTransaction, MyMoneySplit> > 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<QPair<MyMoneyTransaction, MyMoneySplit> > itTransactionSplitResult(result);
MyMoneyMoney transactionsBalance;
while (itTransactionSplitResult.hasNext()) {
const QPair<MyMoneyTransaction, MyMoneySplit> &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<MyMoneyTransaction, MyMoneySplit> &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<MyMoneyMoney> sumList;
sumList << MyMoneyMoney();
QMap<MyMoneyMoney, QList<QPair<QString, QString> > > sumToComponentsMap;
// compute the possible matches
QListIterator<QPair<MyMoneyTransaction, MyMoneySplit> > itTransactionSplit(transactions);
while (itTransactionSplit.hasNext()) {
const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = itTransactionSplit.next();
QListIterator<MyMoneyMoney> itSum(sumList);
QList<MyMoneyMoney> tempList;
while (itSum.hasNext()) {
const MyMoneyMoney &sum = itSum.next();
QList<QPair<QString, QString> > splitIds;
splitIds << qMakePair<QString, QString>(transactionSplit.first.id(), transactionSplit.second.id());
if (sumToComponentsMap.contains(sum)) {
if (sumToComponentsMap.value(sum).contains(qMakePair<QString, QString>(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<MyMoneyMoney> unionList;
unionList.append(tempList);
unionList.append(sumList);
qSort(unionList);
sumList.clear();
MyMoneyMoney smallestSumFromUnion = unionList.first();
sumList.append(smallestSumFromUnion);
QListIterator<MyMoneyMoney> 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<QPair<MyMoneyTransaction, MyMoneySplit> > itTransactionSplit(transactions);
while (itTransactionSplit.hasNext()) {
const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = itTransactionSplit.next();
const QList<QPair<QString, QString> > &splitIds = sumToComponentsMap.value(amount);
if (splitIds.contains(qMakePair<QString, QString>(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::slotAccountReconcileStart()
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount account;
// we cannot reconcile standard accounts
if (!file->isStandardAccount(d->m_selectedAccount.id())) {
// check if we can reconcile this account
// it make's sense for asset and liability accounts
try {
// check if we have overdue schedules for this account
- QList<MyMoneySchedule> schedules = file->scheduleList(d->m_selectedAccount.id(), MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), QDate(), true);
+ QList<MyMoneySchedule> schedules = file->scheduleList(d->m_selectedAccount.id(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, QDate(), QDate(), true);
if (schedules.count() > 0) {
if (KMessageBox::questionYesNo(this, i18n("KMyMoney has detected some overdue scheduled transactions for this account. Do you want to enter those scheduled transactions now?"), i18n("Scheduled transactions found")) == KMessageBox::Yes) {
QMap<QString, bool> skipMap;
bool processedOne;
KMyMoneyUtils::EnterScheduleResultCodeE rc = KMyMoneyUtils::Enter;
do {
processedOne = false;
QList<MyMoneySchedule>::const_iterator it_sch;
for (it_sch = schedules.constBegin(); (rc != KMyMoneyUtils::Cancel) && (it_sch != schedules.constEnd()); ++it_sch) {
MyMoneySchedule sch(*(it_sch));
// and enter it if it is not on the skip list
if (skipMap.find((*it_sch).id()) == skipMap.end()) {
rc = enterSchedule(sch, false, true);
if (rc == KMyMoneyUtils::Ignore) {
skipMap[(*it_sch).id()] = true;
}
}
}
// reload list (maybe this schedule needs to be added again)
- schedules = file->scheduleList(d->m_selectedAccount.id(), MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), QDate(), true);
+ schedules = file->scheduleList(d->m_selectedAccount.id(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, QDate(), QDate(), true);
} while (processedOne);
}
}
account = file->account(d->m_selectedAccount.id());
// get rid of previous run.
delete d->m_endingBalanceDlg;
d->m_endingBalanceDlg = new KEndingBalanceDlg(account, this);
if (account.isAssetLiability()) {
connect(d->m_endingBalanceDlg, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&)));
connect(d->m_endingBalanceDlg, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), this, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount)));
if (d->m_endingBalanceDlg->exec() == QDialog::Accepted) {
if (KMyMoneyGlobalSettings::autoReconciliation()) {
MyMoneyMoney startBalance = d->m_endingBalanceDlg->previousBalance();
MyMoneyMoney endBalance = d->m_endingBalanceDlg->endingBalance();
QDate endDate = d->m_endingBalanceDlg->statementDate();
QList<QPair<MyMoneyTransaction, MyMoneySplit> > transactionList;
MyMoneyTransactionFilter filter(account.id());
- filter.addState(MyMoneyTransactionFilter::cleared);
- filter.addState(MyMoneyTransactionFilter::notReconciled);
+ filter.addState((int)eMyMoney::TransactionFilter::State::Cleared);
+ filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled);
filter.setDateFilter(QDate(), endDate);
filter.setConsiderCategory(false);
filter.setReportAllSplits(true);
file->transactionList(transactionList, filter);
QList<QPair<MyMoneyTransaction, MyMoneySplit> > result = d->automaticReconciliation(account, transactionList, endBalance - startBalance);
if (!result.empty()) {
QString message = i18n("KMyMoney has detected transactions matching your reconciliation data.\nWould you like KMyMoney to clear these transactions for you?");
if (KMessageBox::questionYesNo(this,
message,
i18n("Automatic reconciliation"),
KStandardGuiItem::yes(),
KStandardGuiItem::no(),
"AcceptAutomaticReconciliation") == KMessageBox::Yes) {
// mark the transactions cleared
KMyMoneyRegister::SelectedTransactions oldSelection = d->m_selectedTransactions;
d->m_selectedTransactions.clear();
QListIterator<QPair<MyMoneyTransaction, MyMoneySplit> > itTransactionSplitResult(result);
while (itTransactionSplitResult.hasNext()) {
const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = itTransactionSplitResult.next();
d->m_selectedTransactions.append(KMyMoneyRegister::SelectedTransaction(transactionSplit.first, transactionSplit.second));
}
// mark all transactions in d->m_selectedTransactions as 'Cleared'
markTransaction(MyMoneySplit::Cleared);
d->m_selectedTransactions = oldSelection;
}
}
}
if (d->m_myMoneyView->startReconciliation(account, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance())) {
// check if the user requests us to create interest
// or charge transactions.
MyMoneyTransaction ti = d->m_endingBalanceDlg->interestTransaction();
MyMoneyTransaction tc = d->m_endingBalanceDlg->chargeTransaction();
MyMoneyFileTransaction ft;
try {
if (ti != MyMoneyTransaction()) {
MyMoneyFile::instance()->addTransaction(ti);
}
if (tc != MyMoneyTransaction()) {
MyMoneyFile::instance()->addTransaction(tc);
}
ft.commit();
} catch (const MyMoneyException &e) {
qWarning("interest transaction not stored: '%s'", qPrintable(e.what()));
}
// reload the account object as it might have changed in the meantime
d->m_reconciliationAccount = file->account(account.id());
slotUpdateActions();
}
}
}
} catch (const MyMoneyException &) {
}
}
}
void KMyMoneyApp::slotAccountReconcileFinish()
{
MyMoneyFile* file = MyMoneyFile::instance();
if (!d->m_reconciliationAccount.id().isEmpty()) {
// retrieve list of all transactions that are not reconciled or cleared
QList<QPair<MyMoneyTransaction, MyMoneySplit> > transactionList;
MyMoneyTransactionFilter filter(d->m_reconciliationAccount.id());
- filter.addState(MyMoneyTransactionFilter::cleared);
- filter.addState(MyMoneyTransactionFilter::notReconciled);
+ filter.addState((int)eMyMoney::TransactionFilter::State::Cleared);
+ filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled);
filter.setDateFilter(QDate(), d->m_endingBalanceDlg->statementDate());
filter.setConsiderCategory(false);
filter.setReportAllSplits(true);
file->transactionList(transactionList, filter);
MyMoneyMoney balance = MyMoneyFile::instance()->balance(d->m_reconciliationAccount.id(), d->m_endingBalanceDlg->statementDate());
MyMoneyMoney actBalance, clearedBalance;
actBalance = clearedBalance = balance;
// walk the list of transactions to figure out the balance(s)
QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
for (it = transactionList.constBegin(); it != transactionList.constEnd(); ++it) {
if ((*it).second.reconcileFlag() == MyMoneySplit::NotReconciled) {
clearedBalance -= (*it).second.shares();
}
}
if (d->m_endingBalanceDlg->endingBalance() != clearedBalance) {
QString message = i18n("You are about to finish the reconciliation of this account with a difference between your bank statement and the transactions marked as cleared.\n"
"Are you sure you want to finish the reconciliation?");
if (KMessageBox::questionYesNo(this, message, i18n("Confirm end of reconciliation"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::No)
return;
}
MyMoneyFileTransaction ft;
// refresh object
d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id());
// Turn off reconciliation mode
d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount);
// only update the last statement balance here, if we haven't a newer one due
// to download of online statements.
if (d->m_reconciliationAccount.value("lastImportedTransactionDate").isEmpty()
|| QDate::fromString(d->m_reconciliationAccount.value("lastImportedTransactionDate"), Qt::ISODate) < d->m_endingBalanceDlg->statementDate()) {
d->m_reconciliationAccount.setValue("lastStatementBalance", d->m_endingBalanceDlg->endingBalance().toString());
// in case we override the last statement balance here, we have to make sure
// that we don't show the online balance anymore, as it might be different
d->m_reconciliationAccount.deletePair("lastImportedTransactionDate");
}
d->m_reconciliationAccount.setLastReconciliationDate(d->m_endingBalanceDlg->statementDate());
// keep a record of this reconciliation
d->m_reconciliationAccount.addReconciliation(d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance());
d->m_reconciliationAccount.deletePair("lastReconciledBalance");
d->m_reconciliationAccount.deletePair("statementBalance");
d->m_reconciliationAccount.deletePair("statementDate");
try {
// update the account data
file->modifyAccount(d->m_reconciliationAccount);
/*
// collect the list of cleared splits for this account
filter.clear();
filter.addAccount(d->m_reconciliationAccount.id());
- filter.addState(MyMoneyTransactionFilter::cleared);
+ filter.addState(eMyMoney::TransactionFilter::Cleared);
filter.setConsiderCategory(false);
filter.setReportAllSplits(true);
file->transactionList(transactionList, filter);
*/
// walk the list of transactions/splits and mark the cleared ones as reconciled
QList<QPair<MyMoneyTransaction, MyMoneySplit> >::iterator it;
for (it = transactionList.begin(); it != transactionList.end(); ++it) {
MyMoneySplit sp = (*it).second;
// skip the ones that are not marked cleared
if (sp.reconcileFlag() != MyMoneySplit::Cleared)
continue;
// always retrieve a fresh copy of the transaction because we
// might have changed it already with another split
MyMoneyTransaction t = file->transaction((*it).first.id());
sp.setReconcileFlag(MyMoneySplit::Reconciled);
sp.setReconcileDate(d->m_endingBalanceDlg->statementDate());
t.modifySplit(sp);
// update the engine ...
file->modifyTransaction(t);
// ... and the list
(*it) = qMakePair(t, sp);
}
ft.commit();
// reload account data from engine as the data might have changed in the meantime
d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id());
emit accountReconciled(d->m_reconciliationAccount,
d->m_endingBalanceDlg->statementDate(),
d->m_endingBalanceDlg->previousBalance(),
d->m_endingBalanceDlg->endingBalance(),
transactionList);
} catch (const MyMoneyException &) {
qDebug("Unexpected exception when setting cleared to reconcile");
}
}
// Turn off reconciliation mode
d->m_reconciliationAccount = MyMoneyAccount();
slotUpdateActions();
}
void KMyMoneyApp::slotAccountReconcilePostpone()
{
MyMoneyFileTransaction ft;
MyMoneyFile* file = MyMoneyFile::instance();
if (!d->m_reconciliationAccount.id().isEmpty()) {
// refresh object
d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id());
// Turn off reconciliation mode
d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount);
d->m_reconciliationAccount.setValue("lastReconciledBalance", d->m_endingBalanceDlg->previousBalance().toString());
d->m_reconciliationAccount.setValue("statementBalance", d->m_endingBalanceDlg->endingBalance().toString());
d->m_reconciliationAccount.setValue("statementDate", d->m_endingBalanceDlg->statementDate().toString(Qt::ISODate));
try {
file->modifyAccount(d->m_reconciliationAccount);
ft.commit();
d->m_reconciliationAccount = MyMoneyAccount();
slotUpdateActions();
} catch (const MyMoneyException &) {
qDebug("Unexpected exception when setting last reconcile info into account");
ft.rollback();
d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id());
}
}
}
void KMyMoneyApp::slotAccountOpenEmpty()
{
slotAccountOpen(MyMoneyAccount());
}
void KMyMoneyApp::slotAccountOpen(const MyMoneyObject& obj)
{
if (typeid(obj) != typeid(MyMoneyAccount))
return;
MyMoneyFile* file = MyMoneyFile::instance();
QString id = d->m_selectedAccount.id();
// if the caller passed a non-empty object, we need to select that
if (!obj.id().isEmpty()) {
id = obj.id();
}
// we cannot reconcile standard accounts
if (!file->isStandardAccount(id)) {
// check if we can open this account
// currently it make's sense for asset and liability accounts
try {
MyMoneyAccount account = file->account(id);
d->m_myMoneyView->slotLedgerSelected(account.id());
} catch (const MyMoneyException &) {
}
}
}
void KMyMoneyApp::enableCloseAccountAction(const MyMoneyAccount& acc)
{
QAction *a = actionCollection()->action(s_Actions[Action::AccountClose]);
switch (canCloseAccount(acc)) {
case KMyMoneyUtils::AccountCanClose: {
a->setEnabled(true);
break;
}
case KMyMoneyUtils::AccountBalanceNonZero: {
a->setEnabled(false);
a->setToolTip(i18n("The balance of the account must be zero before the account can be closed"));
break;
}
case KMyMoneyUtils::AccountChildrenOpen: {
a->setEnabled(false);
a->setToolTip(i18n("All subaccounts must be closed before the account can be closed"));
break;
}
case KMyMoneyUtils::AccountScheduleReference: {
a->setEnabled(false);
a->setToolTip(i18n("This account is still included in an active schedule"));
break;
}
}
}
KMyMoneyUtils::CanCloseAccountCodeE KMyMoneyApp::canCloseAccount(const MyMoneyAccount& acc) const
{
// balance must be zero
if (!acc.balance().isZero())
return KMyMoneyUtils::AccountBalanceNonZero;
// all children must be already closed
QStringList::const_iterator it_a;
for (it_a = acc.accountList().constBegin(); it_a != acc.accountList().constEnd(); ++it_a) {
MyMoneyAccount a = MyMoneyFile::instance()->account(*it_a);
if (!a.isClosed()) {
return KMyMoneyUtils::AccountChildrenOpen;
}
}
// there must be no unfinished schedule referencing the account
QList<MyMoneySchedule> list = MyMoneyFile::instance()->scheduleList();
QList<MyMoneySchedule>::const_iterator it_l;
for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) {
if ((*it_l).isFinished())
continue;
if ((*it_l).hasReferenceTo(acc.id()))
return KMyMoneyUtils::AccountScheduleReference;
}
return KMyMoneyUtils::AccountCanClose;
}
void KMyMoneyApp::slotAccountClose()
{
MyMoneyAccount a;
if (!d->m_selectedInvestment.id().isEmpty())
a = d->m_selectedInvestment;
else if (!d->m_selectedAccount.id().isEmpty())
a = d->m_selectedAccount;
if (a.id().isEmpty())
return; // need an account ID
MyMoneyFileTransaction ft;
try {
a.setClosed(true);
MyMoneyFile::instance()->modifyAccount(a);
ft.commit();
if (KMyMoneyGlobalSettings::hideClosedAccounts()) {
KMessageBox::information(this, QString("<qt>") + i18n("You have closed this account. It remains in the system because you have transactions which still refer to it, but it is not shown in the views. You can make it visible again by going to the View menu and selecting <b>Show all accounts</b> or by deselecting the <b>Do not show closed accounts</b> setting.") + QString("</qt>"), i18n("Information"), "CloseAccountInfo");
}
} catch (const MyMoneyException &) {
}
}
void KMyMoneyApp::slotAccountReopen()
{
MyMoneyAccount a;
if (!d->m_selectedInvestment.id().isEmpty())
a = d->m_selectedInvestment;
else if (!d->m_selectedAccount.id().isEmpty())
a = d->m_selectedAccount;
if (a.id().isEmpty())
return; // need an account ID
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyFileTransaction ft;
try {
while (a.isClosed()) {
a.setClosed(false);
file->modifyAccount(a);
a = file->account(a.parentAccountId());
}
ft.commit();
} catch (const MyMoneyException &) {
}
}
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("<p><b>%1</b> cannot be moved to institution <b>%2</b>. Reason: %3</p>", 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("<p><b>%1</b> cannot be moved to <b>%2</b>. Reason: %3</p>", src.name(), dst.name(), e.what()));
}
}
void KMyMoneyApp::slotAccountTransactionReport()
{
// Generate a transaction report that contains transactions for only the
// currently selected account.
if (!d->m_selectedAccount.id().isEmpty()) {
MyMoneyReport report(
MyMoneyReport::eAccount,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("%1 YTD Account Transactions", d->m_selectedAccount.name()),
i18n("Generated Report")
);
report.setGroup(i18n("Transactions"));
report.addAccount(d->m_selectedAccount.id());
d->m_myMoneyView->slotShowReport(report);
}
}
void KMyMoneyApp::slotScheduleNew()
{
slotScheduleNew(MyMoneyTransaction());
}
-void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, MyMoneySchedule::occurrenceE occurrence)
+void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence)
{
MyMoneySchedule schedule;
schedule.setOccurrence(occurrence);
// if the schedule is based on an existing transaction,
// we take the post date and project it to the next
// schedule in a month.
if (_t != MyMoneyTransaction()) {
MyMoneyTransaction t(_t);
schedule.setTransaction(t);
- if (occurrence != MyMoneySchedule::OCCUR_ONCE)
+ if (occurrence != eMyMoney::Schedule::Occurrence::Once)
schedule.setNextDueDate(schedule.nextPayment(t.postDate()));
}
QPointer<KEditScheduleDlg> dlg = new KEditScheduleDlg(schedule, this);
TransactionEditor* transactionEditor = dlg->startEdit();
if (transactionEditor) {
KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart());
if (dlg->exec() == QDialog::Accepted && dlg != 0) {
MyMoneyFileTransaction ft;
try {
schedule = dlg->schedule();
MyMoneyFile::instance()->addSchedule(schedule);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::error(this, i18n("Unable to add scheduled transaction: %1", e.what()), i18n("Add scheduled transaction"));
}
}
}
delete transactionEditor;
delete dlg;
}
void KMyMoneyApp::slotScheduleEdit()
{
if (!d->m_selectedSchedule.id().isEmpty()) {
try {
MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id());
KEditScheduleDlg* sched_dlg = 0;
KEditLoanWizard* loan_wiz = 0;
switch (schedule.type()) {
- case MyMoneySchedule::TYPE_BILL:
- case MyMoneySchedule::TYPE_DEPOSIT:
- case MyMoneySchedule::TYPE_TRANSFER:
+ case eMyMoney::Schedule::Type::Bill:
+ case eMyMoney::Schedule::Type::Deposit:
+ case eMyMoney::Schedule::Type::Transfer:
sched_dlg = new KEditScheduleDlg(schedule, this);
d->m_transactionEditor = sched_dlg->startEdit();
if (d->m_transactionEditor) {
KMyMoneyMVCCombo::setSubstringSearchForChildren(sched_dlg, !KMyMoneySettings::stringMatchFromStart());
if (sched_dlg->exec() == QDialog::Accepted) {
MyMoneyFileTransaction ft;
try {
MyMoneySchedule sched = sched_dlg->schedule();
// Check whether the new Schedule Date
// is at or before the lastPaymentDate
// If it is, ask the user whether to clear the
// lastPaymentDate
const QDate& next = sched.nextDueDate();
const QDate& last = sched.lastPayment();
if (next.isValid() && last.isValid() && next <= last) {
// Entered a date effectively no later
// than previous payment. Date would be
// updated automatically so we probably
// want to clear it. Let's ask the user.
if (KMessageBox::questionYesNo(this, QString("<qt>") + i18n("You have entered a scheduled transaction date of <b>%1</b>. Because the scheduled transaction was last paid on <b>%2</b>, KMyMoney will automatically adjust the scheduled transaction date to the next date unless the last payment date is reset. Do you want to reset the last payment date?", QLocale().toString(next, QLocale::ShortFormat), QLocale().toString(last, QLocale::ShortFormat)) + QString("</qt>"), i18n("Reset Last Payment Date"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::Yes) {
sched.setLastPayment(QDate());
}
}
MyMoneyFile::instance()->modifySchedule(sched);
// delete the editor before we emit the dataChanged() signal from the
// engine. Calling this twice in a row does not hurt.
deleteTransactionEditor();
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what());
}
}
deleteTransactionEditor();
}
delete sched_dlg;
break;
- case MyMoneySchedule::TYPE_LOANPAYMENT:
+ case eMyMoney::Schedule::Type::LoanPayment:
loan_wiz = new KEditLoanWizard(schedule.account(2));
connect(loan_wiz, SIGNAL(newCategory(MyMoneyAccount&)), this, SLOT(slotCategoryNew(MyMoneyAccount&)));
connect(loan_wiz, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&)));
if (loan_wiz->exec() == QDialog::Accepted) {
MyMoneyFileTransaction ft;
try {
MyMoneyFile::instance()->modifySchedule(loan_wiz->schedule());
MyMoneyFile::instance()->modifyAccount(loan_wiz->account());
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what());
}
}
delete loan_wiz;
break;
- case MyMoneySchedule::TYPE_ANY:
+ case eMyMoney::Schedule::Type::Any:
break;
}
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what());
}
}
}
void KMyMoneyApp::slotScheduleDelete()
{
if (!d->m_selectedSchedule.id().isEmpty()) {
MyMoneyFileTransaction ft;
try {
MyMoneySchedule sched = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id());
QString msg = i18n("<p>Are you sure you want to delete the scheduled transaction <b>%1</b>?</p>", d->m_selectedSchedule.name());
- if (sched.type() == MyMoneySchedule::TYPE_LOANPAYMENT) {
+ if (sched.type() == eMyMoney::Schedule::Type::LoanPayment) {
msg += QString(" ");
msg += i18n("In case of loan payments it is currently not possible to recreate the scheduled transaction.");
}
if (KMessageBox::questionYesNo(this, msg) == KMessageBox::No)
return;
MyMoneyFile::instance()->removeSchedule(sched);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Unable to remove scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what());
}
}
}
void KMyMoneyApp::slotScheduleDuplicate()
{
// since we may jump here via code, we have to make sure to react only
// if the action is enabled
if (kmymoney->actionCollection()->action(s_Actions[Action::ScheduleDuplicate])->isEnabled()) {
MyMoneySchedule sch = d->m_selectedSchedule;
sch.clearId();
sch.setLastPayment(QDate());
sch.setName(i18nc("Copy of scheduled transaction name", "Copy of %1", sch.name()));
// make sure that we set a valid next due date if the original next due date is invalid
if (!sch.nextDueDate().isValid())
sch.setNextDueDate(QDate::currentDate());
MyMoneyFileTransaction ft;
try {
MyMoneyFile::instance()->addSchedule(sch);
ft.commit();
// select the new schedule in the view
if (!d->m_selectedSchedule.id().isEmpty())
d->m_myMoneyView->slotScheduleSelected(sch.id());
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to duplicate scheduled transaction: '%1'", d->m_selectedSchedule.name()), e.what());
}
}
}
void KMyMoneyApp::slotScheduleSkip()
{
if (!d->m_selectedSchedule.id().isEmpty()) {
try {
MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id());
skipSchedule(schedule);
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Unknown scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what());
}
}
}
void KMyMoneyApp::skipSchedule(MyMoneySchedule& schedule)
{
if (!schedule.id().isEmpty()) {
try {
schedule = MyMoneyFile::instance()->schedule(schedule.id());
if (!schedule.isFinished()) {
- if (schedule.occurrence() != MyMoneySchedule::OCCUR_ONCE) {
+ if (schedule.occurrence() != eMyMoney::Schedule::Occurrence::Once) {
QDate next = schedule.nextDueDate();
if (!schedule.isFinished() && (KMessageBox::questionYesNo(this, QString("<qt>") + i18n("Do you really want to skip the <b>%1</b> transaction scheduled for <b>%2</b>?", schedule.name(), QLocale().toString(next, QLocale::ShortFormat)) + QString("</qt>"))) == KMessageBox::Yes) {
MyMoneyFileTransaction ft;
schedule.setLastPayment(next);
schedule.setNextDueDate(schedule.nextPayment(next));
MyMoneyFile::instance()->modifySchedule(schedule);
ft.commit();
}
}
}
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, QString("<qt>") + i18n("Unable to skip scheduled transaction <b>%1</b>.", schedule.name()) + QString("</qt>"), e.what());
}
}
}
void KMyMoneyApp::slotScheduleEnter()
{
if (!d->m_selectedSchedule.id().isEmpty()) {
try {
MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id());
enterSchedule(schedule);
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Unknown scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what());
}
}
}
KMyMoneyUtils::EnterScheduleResultCodeE KMyMoneyApp::enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys)
{
KMyMoneyUtils::EnterScheduleResultCodeE rc = KMyMoneyUtils::Cancel;
if (!schedule.id().isEmpty()) {
try {
schedule = MyMoneyFile::instance()->schedule(schedule.id());
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what());
return rc;
}
QPointer<KEnterScheduleDlg> dlg = new KEnterScheduleDlg(this, schedule);
try {
QDate origDueDate = schedule.nextDueDate();
dlg->showExtendedKeys(extendedKeys);
d->m_transactionEditor = dlg->startEdit();
if (d->m_transactionEditor) {
KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart());
MyMoneyTransaction torig, taccepted;
d->m_transactionEditor->createTransaction(torig, dlg->transaction(),
schedule.transaction().splits().isEmpty() ? MyMoneySplit() : schedule.transaction().splits().front(), true);
// force actions to be available no matter what (will be updated according to the state during
// slotTransactionsEnter or slotTransactionsCancel)
kmymoney->actionCollection()->action(s_Actions[Action::TransactionCancel])->setEnabled(true);
kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->setEnabled(true);
KConfirmManualEnterDlg::Action action = KConfirmManualEnterDlg::ModifyOnce;
if (!autoEnter || !schedule.isFixed()) {
for (; dlg != 0;) {
rc = KMyMoneyUtils::Cancel;
if (dlg->exec() == QDialog::Accepted && dlg != 0) {
rc = dlg->resultCode();
if (rc == KMyMoneyUtils::Enter) {
d->m_transactionEditor->createTransaction(taccepted, torig, torig.splits().isEmpty() ? MyMoneySplit() : torig.splits().front(), true);
// make sure to suppress comparison of some data: postDate
torig.setPostDate(taccepted.postDate());
if (torig != taccepted) {
QPointer<KConfirmManualEnterDlg> cdlg =
new KConfirmManualEnterDlg(schedule, this);
cdlg->loadTransactions(torig, taccepted);
if (cdlg->exec() == QDialog::Accepted) {
action = cdlg->action();
delete cdlg;
break;
}
delete cdlg;
// the user has chosen 'cancel' during confirmation,
// we go back to the editor
continue;
}
} else if (rc == KMyMoneyUtils::Skip) {
slotTransactionsCancel();
skipSchedule(schedule);
} else {
slotTransactionsCancel();
}
} else {
if (autoEnter) {
if (KMessageBox::warningYesNo(this, i18n("Are you sure you wish to stop this scheduled transaction from being entered into the register?\n\nKMyMoney will prompt you again next time it starts unless you manually enter it later.")) == KMessageBox::No) {
// the user has chosen 'No' for the above question,
// we go back to the editor
continue;
}
}
slotTransactionsCancel();
}
break;
}
}
// if we still have the editor around here, the user did not cancel
if ((d->m_transactionEditor != 0) && (dlg != 0)) {
MyMoneyFileTransaction ft;
try {
MyMoneyTransaction t;
// add the new transaction
switch (action) {
case KConfirmManualEnterDlg::UseOriginal:
// setup widgets with original transaction data
d->m_transactionEditor->setTransaction(dlg->transaction(), dlg->transaction().splits().isEmpty() ? MyMoneySplit() : dlg->transaction().splits().front());
// and create a transaction based on that data
taccepted = MyMoneyTransaction();
d->m_transactionEditor->createTransaction(taccepted, dlg->transaction(),
dlg->transaction().splits().isEmpty() ? MyMoneySplit() : dlg->transaction().splits().front(), true);
break;
case KConfirmManualEnterDlg::ModifyAlways:
torig = taccepted;
torig.setPostDate(origDueDate);
schedule.setTransaction(torig);
break;
case KConfirmManualEnterDlg::ModifyOnce:
break;
}
QString newId;
connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString)));
if (d->m_transactionEditor->enterTransactions(newId, false)) {
if (!newId.isEmpty()) {
MyMoneyTransaction t = MyMoneyFile::instance()->transaction(newId);
schedule.setLastPayment(t.postDate());
}
// in case the next due date is invalid, the schedule is finished
// we mark it as such by setting the next due date to one day past the end
QDate nextDueDate = schedule.nextPayment(origDueDate);
if (!nextDueDate.isValid()) {
schedule.setNextDueDate(schedule.endDate().addDays(1));
} else {
schedule.setNextDueDate(nextDueDate);
}
MyMoneyFile::instance()->modifySchedule(schedule);
rc = KMyMoneyUtils::Enter;
// delete the editor before we emit the dataChanged() signal from the
// engine. Calling this twice in a row does not hurt.
deleteTransactionEditor();
ft.commit();
}
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what());
}
deleteTransactionEditor();
}
}
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what());
}
delete dlg;
}
return rc;
}
bool KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id)
{
bool doit = true;
if (newnameBase != i18n("New Payee")) {
// Ask the user if that is what he intended to do?
QString msg = QLatin1String("<qt>") + i18n("Do you want to add <b>%1</b> as payer/receiver?", newnameBase) + QLatin1String("</qt>");
if (KMessageBox::questionYesNo(this, msg, i18n("New payee/receiver"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewPayee") == KMessageBox::No) {
doit = false;
// we should not keep the 'no' setting because that can confuse people like
// I have seen in some usability tests. So we just delete it right away.
KSharedConfigPtr kconfig = KSharedConfig::openConfig();
if (kconfig) {
kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewPayee"));
}
}
}
if (doit) {
MyMoneyFileTransaction ft;
try {
QString newname(newnameBase);
// adjust name until a unique name has been created
int count = 0;
for (;;) {
try {
MyMoneyFile::instance()->payeeByName(newname);
newname = QString("%1 [%2]").arg(newnameBase).arg(++count);
} catch (const MyMoneyException &) {
break;
}
}
MyMoneyPayee p;
p.setName(newname);
MyMoneyFile::instance()->addPayee(p);
id = p.id();
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Unable to add payee"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
doit = false;
}
}
return doit;
}
void KMyMoneyApp::slotPayeeNew()
{
QString id;
slotPayeeNew(i18n("New Payee"), id);
// the callbacks should have made sure, that the payees view has been
// updated already. So we search for the id in the list of items
// and select it.
emit payeeCreated(id);
}
bool KMyMoneyApp::payeeInList(const QList<MyMoneyPayee>& list, const QString& id) const
{
bool rc = false;
QList<MyMoneyPayee>::const_iterator it_p = list.begin();
while (it_p != list.end()) {
if ((*it_p).id() == id) {
rc = true;
break;
}
++it_p;
}
return rc;
}
void KMyMoneyApp::slotPayeeDelete()
{
if (d->m_selectedPayees.isEmpty())
return; // shouldn't happen
// get confirmation from user
QString prompt;
if (d->m_selectedPayees.size() == 1)
prompt = i18n("<p>Do you really want to remove the payee <b>%1</b>?</p>", d->m_selectedPayees.front().name());
else
prompt = i18n("Do you really want to remove all selected payees?");
if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Payee")) == KMessageBox::No)
return;
payeeReassign(KPayeeReassignDlg::TypeDelete);
}
void KMyMoneyApp::slotPayeeMerge()
{
if (d->m_selectedPayees.size() < 1)
return; // shouldn't happen
if (KMessageBox::questionYesNo(this, i18n("<p>Do you really want to merge the selected payees?"),
i18n("Merge Payees")) == KMessageBox::No)
return;
if (payeeReassign(KPayeeReassignDlg::TypeMerge))
// clean selection since we just deleted the selected payees
slotSelectPayees(QList<MyMoneyPayee>());
}
bool KMyMoneyApp::payeeReassign(int type)
{
if (!(type >= 0 && type < KPayeeReassignDlg::TypeCount))
return false;
MyMoneyFile * file = MyMoneyFile::instance();
MyMoneyFileTransaction ft;
try {
// create a transaction filter that contains all payees selected for removal
MyMoneyTransactionFilter f = MyMoneyTransactionFilter();
for (QList<MyMoneyPayee>::const_iterator it = d->m_selectedPayees.constBegin();
it != d->m_selectedPayees.constEnd(); ++it) {
f.addPayee((*it).id());
}
// request a list of all transactions that still use the payees in question
QList<MyMoneyTransaction> translist = file->transactionList(f);
// qDebug() << "[KPayeesView::slotDeletePayee] " << translist.count() << " transaction still assigned to payees";
// now get a list of all schedules that make use of one of the payees
QList<MyMoneySchedule> all_schedules = file->scheduleList();
QList<MyMoneySchedule> used_schedules;
for (QList<MyMoneySchedule>::ConstIterator it = all_schedules.constBegin();
it != all_schedules.constEnd(); ++it) {
// loop over all splits in the transaction of the schedule
for (QList<MyMoneySplit>::ConstIterator s_it = (*it).transaction().splits().constBegin();
s_it != (*it).transaction().splits().constEnd(); ++s_it) {
// is the payee in the split to be deleted?
if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) {
used_schedules.push_back(*it); // remember this schedule
break;
}
}
}
// qDebug() << "[KPayeesView::slotDeletePayee] " << used_schedules.count() << " schedules use one of the selected payees";
// and a list of all loan accounts that references one of the payees
QList<MyMoneyAccount> allAccounts;
QList<MyMoneyAccount> usedAccounts;
file->accountList(allAccounts);
foreach (const MyMoneyAccount &account, allAccounts) {
if (account.isLoan()) {
MyMoneyAccountLoan loanAccount(account);
foreach (const MyMoneyPayee &payee, d->m_selectedPayees) {
if (loanAccount.hasReferenceTo(payee.id())) {
usedAccounts.append(account);
}
}
}
}
MyMoneyPayee newPayee;
bool addToMatchList = false;
// if at least one payee is still referenced, we need to reassign its transactions first
if (!translist.isEmpty() || !used_schedules.isEmpty() || !usedAccounts.isEmpty()) {
// first create list with all non-selected payees
QList<MyMoneyPayee> remainingPayees;
if (type == KPayeeReassignDlg::TypeMerge) {
remainingPayees = d->m_selectedPayees;
} else {
remainingPayees = file->payeeList();
QList<MyMoneyPayee>::iterator it_p;
for (it_p = remainingPayees.begin(); it_p != remainingPayees.end();) {
if (d->m_selectedPayees.contains(*it_p)) {
it_p = remainingPayees.erase(it_p);
} else {
++it_p;
}
}
}
// show error message if no payees remain
if (remainingPayees.isEmpty()) {
KMessageBox::sorry(this, i18n("At least one transaction/scheduled transaction or loan account is still referenced by a payee. "
"Currently you have all payees selected. However, at least one payee must remain so "
"that the transaction/scheduled transaction or loan account can be reassigned."));
return false;
}
// show transaction reassignment dialog
KPayeeReassignDlg * dlg = new KPayeeReassignDlg(static_cast<KPayeeReassignDlg::OperationType>(type), this);
KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart());
QString payee_id = dlg->show(remainingPayees);
addToMatchList = dlg->addToMatchList();
delete dlg; // and kill the dialog
if (payee_id.isEmpty())
return false; // the user aborted the dialog, so let's abort as well
// try to get selected payee. If not possible and we are merging payees,
// then we create a new one
try {
newPayee = file->payee(payee_id);
} catch (const MyMoneyException &e) {
if (type == KPayeeReassignDlg::TypeMerge) {
// it's ok to use payee_id for both arguments since the first is const,
// so it's garantee not to change its content
if (!slotPayeeNew(payee_id, payee_id))
return false; // the user aborted the dialog, so let's abort as well
newPayee = file->payee(payee_id);
} else {
return false;
}
}
// TODO : check if we have a report that explicitively uses one of our payees
// and issue an appropriate warning
try {
QList<MyMoneySplit>::iterator s_it;
// now loop over all transactions and reassign payee
for (QList<MyMoneyTransaction>::iterator it = translist.begin(); it != translist.end(); ++it) {
// create a copy of the splits list in the transaction
QList<MyMoneySplit> splits = (*it).splits();
// loop over all splits
for (s_it = splits.begin(); s_it != splits.end(); ++s_it) {
// if the split is assigned to one of the selected payees, we need to modify it
if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) {
(*s_it).setPayeeId(payee_id); // first modify payee in current split
// then modify the split in our local copy of the transaction list
(*it).modifySplit(*s_it); // this does not modify the list object 'splits'!
}
} // for - Splits
file->modifyTransaction(*it); // modify the transaction in the MyMoney object
} // for - Transactions
// now loop over all schedules and reassign payees
for (QList<MyMoneySchedule>::iterator it = used_schedules.begin();
it != used_schedules.end(); ++it) {
// create copy of transaction in current schedule
MyMoneyTransaction trans = (*it).transaction();
// create copy of lists of splits
QList<MyMoneySplit> splits = trans.splits();
for (s_it = splits.begin(); s_it != splits.end(); ++s_it) {
if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) {
(*s_it).setPayeeId(payee_id);
trans.modifySplit(*s_it); // does not modify the list object 'splits'!
}
} // for - Splits
// store transaction in current schedule
(*it).setTransaction(trans);
file->modifySchedule(*it); // modify the schedule in the MyMoney engine
} // for - Schedules
// reassign the payees in the loans that reference the deleted payees
foreach (const MyMoneyAccount &account, usedAccounts) {
MyMoneyAccountLoan loanAccount(account);
loanAccount.setPayee(payee_id);
file->modifyAccount(loanAccount);
}
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to reassign payee of transaction/split"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
}
} else { // if !translist.isEmpty()
if (type == KPayeeReassignDlg::TypeMerge) {
KMessageBox::sorry(this, i18n("Nothing to merge."), i18n("Merge Payees"));
return false;
}
}
bool ignorecase;
QStringList payeeNames;
MyMoneyPayee::payeeMatchType matchType = newPayee.matchData(ignorecase, payeeNames);
QStringList deletedPayeeNames;
// now loop over all selected payees and remove them
for (QList<MyMoneyPayee>::iterator it = d->m_selectedPayees.begin();
it != d->m_selectedPayees.end(); ++it) {
if (newPayee.id() != (*it).id()) {
if (addToMatchList) {
deletedPayeeNames << (*it).name();
}
file->removePayee(*it);
}
}
// if we initially have no matching turned on, we just ignore the case (default)
if (matchType == MyMoneyPayee::matchDisabled)
ignorecase = true;
// update the destination payee if this was requested by the user
if (addToMatchList && deletedPayeeNames.count() > 0) {
// add new names to the list
// TODO: it would be cool to somehow shrink the list to make better use
// of regular expressions at this point. For now, we leave this task
// to the user himeself.
QStringList::const_iterator it_n;
for (it_n = deletedPayeeNames.constBegin(); it_n != deletedPayeeNames.constEnd(); ++it_n) {
if (matchType == MyMoneyPayee::matchKey) {
// make sure we really need it and it is not caught by an existing regexp
QStringList::const_iterator it_k;
for (it_k = payeeNames.constBegin(); it_k != payeeNames.constEnd(); ++it_k) {
QRegExp exp(*it_k, ignorecase ? Qt::CaseInsensitive : Qt::CaseSensitive);
if (exp.indexIn(*it_n) != -1)
break;
}
if (it_k == payeeNames.constEnd())
payeeNames << QRegExp::escape(*it_n);
} else if (payeeNames.contains(*it_n) == 0)
payeeNames << QRegExp::escape(*it_n);
}
// and update the payee in the engine context
// make sure to turn on matching for this payee in the right mode
newPayee.setMatchData(MyMoneyPayee::matchKey, ignorecase, payeeNames);
file->modifyPayee(newPayee);
}
ft.commit();
// If we just deleted the payees, they sure don't exist anymore
slotSelectPayees(QList<MyMoneyPayee>());
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to remove payee(s)"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
}
return true;
}
void KMyMoneyApp::slotTagNew(const QString& newnameBase, QString& id)
{
bool doit = true;
if (newnameBase != i18n("New Tag")) {
// Ask the user if that is what he intended to do?
QString msg = QString("<qt>") + i18n("Do you want to add <b>%1</b> as tag?", newnameBase) + QString("</qt>");
if (KMessageBox::questionYesNo(this, msg, i18n("New tag"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewTag") == KMessageBox::No) {
doit = false;
// we should not keep the 'no' setting because that can confuse people like
// I have seen in some usability tests. So we just delete it right away.
KSharedConfigPtr kconfig = KSharedConfig::openConfig();
if (kconfig) {
kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewTag"));
}
}
}
if (doit) {
MyMoneyFileTransaction ft;
try {
QString newname(newnameBase);
// adjust name until a unique name has been created
int count = 0;
for (;;) {
try {
MyMoneyFile::instance()->tagByName(newname);
newname = QString("%1 [%2]").arg(newnameBase).arg(++count);
} catch (const MyMoneyException &) {
break;
}
}
MyMoneyTag ta;
ta.setName(newname);
MyMoneyFile::instance()->addTag(ta);
id = ta.id();
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Unable to add tag"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
}
void KMyMoneyApp::slotTagNew()
{
QString id;
slotTagNew(i18n("New Tag"), id);
// the callbacks should have made sure, that the tags view has been
// updated already. So we search for the id in the list of items
// and select it.
emit tagCreated(id);
}
bool KMyMoneyApp::tagInList(const QList<MyMoneyTag>& list, const QString& id) const
{
bool rc = false;
QList<MyMoneyTag>::const_iterator it_p = list.begin();
while (it_p != list.end()) {
if ((*it_p).id() == id) {
rc = true;
break;
}
++it_p;
}
return rc;
}
void KMyMoneyApp::slotTagDelete()
{
if (d->m_selectedTags.isEmpty())
return; // shouldn't happen
MyMoneyFile * file = MyMoneyFile::instance();
// first create list with all non-selected tags
QList<MyMoneyTag> remainingTags = file->tagList();
QList<MyMoneyTag>::iterator it_ta;
for (it_ta = remainingTags.begin(); it_ta != remainingTags.end();) {
if (d->m_selectedTags.contains(*it_ta)) {
it_ta = remainingTags.erase(it_ta);
} else {
++it_ta;
}
}
// get confirmation from user
QString prompt;
if (d->m_selectedTags.size() == 1)
prompt = i18n("<p>Do you really want to remove the tag <b>%1</b>?</p>", d->m_selectedTags.front().name());
else
prompt = i18n("Do you really want to remove all selected tags?");
if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Tag")) == KMessageBox::No)
return;
MyMoneyFileTransaction ft;
try {
// create a transaction filter that contains all tags selected for removal
MyMoneyTransactionFilter f = MyMoneyTransactionFilter();
for (QList<MyMoneyTag>::const_iterator it = d->m_selectedTags.constBegin();
it != d->m_selectedTags.constEnd(); ++it) {
f.addTag((*it).id());
}
// request a list of all transactions that still use the tags in question
QList<MyMoneyTransaction> translist = file->transactionList(f);
// qDebug() << "[KTagsView::slotDeleteTag] " << translist.count() << " transaction still assigned to tags";
// now get a list of all schedules that make use of one of the tags
QList<MyMoneySchedule> all_schedules = file->scheduleList();
QList<MyMoneySchedule> used_schedules;
for (QList<MyMoneySchedule>::ConstIterator it = all_schedules.constBegin();
it != all_schedules.constEnd(); ++it) {
// loop over all splits in the transaction of the schedule
for (QList<MyMoneySplit>::ConstIterator s_it = (*it).transaction().splits().constBegin();
s_it != (*it).transaction().splits().constEnd(); ++s_it) {
for (int i = 0; i < (*s_it).tagIdList().size(); i++) {
// is the tag in the split to be deleted?
if (tagInList(d->m_selectedTags, (*s_it).tagIdList()[i])) {
used_schedules.push_back(*it); // remember this schedule
break;
}
}
}
}
// qDebug() << "[KTagsView::slotDeleteTag] " << used_schedules.count() << " schedules use one of the selected tags";
MyMoneyTag newTag;
// if at least one tag is still referenced, we need to reassign its transactions first
if (!translist.isEmpty() || !used_schedules.isEmpty()) {
// show error message if no tags remain
//FIXME-ALEX Tags are optional so we can delete all of them and simply delete every tagId from every transaction
if (remainingTags.isEmpty()) {
KMessageBox::sorry(this, i18n("At least one transaction/scheduled transaction is still referenced by a tag. "
"Currently you have all tags selected. However, at least one tag must remain so "
"that the transaction/scheduled transaction can be reassigned."));
return;
}
// show transaction reassignment dialog
KTagReassignDlg * dlg = new KTagReassignDlg(this);
KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart());
QString tag_id = dlg->show(remainingTags);
delete dlg; // and kill the dialog
if (tag_id.isEmpty()) //FIXME-ALEX Let the user choose to not reassign a to-be deleted tag to another one.
return; // the user aborted the dialog, so let's abort as well
newTag = file->tag(tag_id);
// TODO : check if we have a report that explicitively uses one of our tags
// and issue an appropriate warning
try {
QList<MyMoneySplit>::iterator s_it;
// now loop over all transactions and reassign tag
for (QList<MyMoneyTransaction>::iterator it = translist.begin(); it != translist.end(); ++it) {
// create a copy of the splits list in the transaction
QList<MyMoneySplit> splits = (*it).splits();
// loop over all splits
for (s_it = splits.begin(); s_it != splits.end(); ++s_it) {
QList<QString> tagIdList = (*s_it).tagIdList();
for (int i = 0; i < tagIdList.size(); i++) {
// if the split is assigned to one of the selected tags, we need to modify it
if (tagInList(d->m_selectedTags, tagIdList[i])) {
tagIdList.removeAt(i);
if (tagIdList.indexOf(tag_id) == -1)
tagIdList.append(tag_id);
i = -1; // restart from the first element
}
}
(*s_it).setTagIdList(tagIdList); // first modify tag list in current split
// then modify the split in our local copy of the transaction list
(*it).modifySplit(*s_it); // this does not modify the list object 'splits'!
} // for - Splits
file->modifyTransaction(*it); // modify the transaction in the MyMoney object
} // for - Transactions
// now loop over all schedules and reassign tags
for (QList<MyMoneySchedule>::iterator it = used_schedules.begin();
it != used_schedules.end(); ++it) {
// create copy of transaction in current schedule
MyMoneyTransaction trans = (*it).transaction();
// create copy of lists of splits
QList<MyMoneySplit> splits = trans.splits();
for (s_it = splits.begin(); s_it != splits.end(); ++s_it) {
QList<QString> tagIdList = (*s_it).tagIdList();
for (int i = 0; i < tagIdList.size(); i++) {
if (tagInList(d->m_selectedTags, tagIdList[i])) {
tagIdList.removeAt(i);
if (tagIdList.indexOf(tag_id) == -1)
tagIdList.append(tag_id);
i = -1; // restart from the first element
}
}
(*s_it).setTagIdList(tagIdList);
trans.modifySplit(*s_it); // does not modify the list object 'splits'!
} // for - Splits
// store transaction in current schedule
(*it).setTransaction(trans);
file->modifySchedule(*it); // modify the schedule in the MyMoney engine
} // for - Schedules
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to reassign tag of transaction/split"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
}
} // if !translist.isEmpty()
// now loop over all selected tags and remove them
for (QList<MyMoneyTag>::iterator it = d->m_selectedTags.begin();
it != d->m_selectedTags.end(); ++it) {
file->removeTag(*it);
}
ft.commit();
// If we just deleted the tags, they sure don't exist anymore
slotSelectTags(QList<MyMoneyTag>());
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to remove tag(s)"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
void KMyMoneyApp::slotCurrencyNew()
{
QString sid = QInputDialog::getText(0, i18n("New currency"), i18n("Enter ISO 4217 code for the new currency"));
if (!sid.isEmpty()) {
QString id(sid);
MyMoneySecurity currency(id, i18n("New currency"));
MyMoneyFileTransaction ft;
try {
MyMoneyFile::instance()->addCurrency(currency);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::sorry(this, i18n("Cannot create new currency. %1", e.what()), i18n("New currency"));
}
emit currencyCreated(id);
}
}
void KMyMoneyApp::slotCurrencyUpdate(const QString &currencyId, const QString& currencyName, const QString& currencyTradingSymbol)
{
MyMoneyFile* file = MyMoneyFile::instance();
try {
if (currencyName != d->m_selectedCurrency.name() || currencyTradingSymbol != d->m_selectedCurrency.tradingSymbol()) {
MyMoneySecurity currency = file->currency(currencyId);
currency.setName(currencyName);
currency.setTradingSymbol(currencyTradingSymbol);
MyMoneyFileTransaction ft;
try {
file->modifyCurrency(currency);
d->m_selectedCurrency = currency;
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::sorry(this, i18n("Cannot update currency. %1", e.what()), i18n("Update currency"));
}
}
} catch (const MyMoneyException &e) {
KMessageBox::sorry(this, i18n("Cannot update currency. %1", e.what()), i18n("Update currency"));
}
}
void KMyMoneyApp::slotCurrencyDelete()
{
if (!d->m_selectedCurrency.id().isEmpty()) {
MyMoneyFileTransaction ft;
try {
MyMoneyFile::instance()->removeCurrency(d->m_selectedCurrency);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::sorry(this, i18n("Cannot delete currency %1. %2", d->m_selectedCurrency.name(), e.what()), i18n("Delete currency"));
}
}
}
void KMyMoneyApp::slotCurrencySetBase()
{
if (!d->m_selectedCurrency.id().isEmpty()) {
if (d->m_selectedCurrency.id() != MyMoneyFile::instance()->baseCurrency().id()) {
MyMoneyFileTransaction ft;
try {
MyMoneyFile::instance()->setBaseCurrency(d->m_selectedCurrency);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2", d->m_selectedCurrency.name(), e.what()), i18n("Set base currency"));
}
}
}
}
void KMyMoneyApp::slotBudgetNew()
{
QDate date = QDate::currentDate();
date.setDate(date.year(), KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay());
QString newname = i18n("Budget %1", date.year());
MyMoneyBudget budget;
// make sure we have a unique name
try {
int i = 1;
// Exception thrown when the name is not found
while (1) {
MyMoneyFile::instance()->budgetByName(newname);
newname = i18n("Budget %1 %2", date.year(), i++);
}
} catch (const MyMoneyException &) {
// all ok, the name is unique
}
MyMoneyFileTransaction ft;
try {
budget.setName(newname);
budget.setBudgetStart(date);
MyMoneyFile::instance()->addBudget(budget);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to add budget: %1, thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
void KMyMoneyApp::slotBudgetDelete()
{
if (d->m_selectedBudgets.isEmpty())
return; // shouldn't happen
MyMoneyFile * file = MyMoneyFile::instance();
// get confirmation from user
QString prompt;
if (d->m_selectedBudgets.size() == 1)
prompt = i18n("<p>Do you really want to remove the budget <b>%1</b>?</p>", d->m_selectedBudgets.front().name());
else
prompt = i18n("Do you really want to remove all selected budgets?");
if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Budget")) == KMessageBox::No)
return;
MyMoneyFileTransaction ft;
try {
// now loop over all selected budgets and remove them
for (QList<MyMoneyBudget>::iterator it = d->m_selectedBudgets.begin();
it != d->m_selectedBudgets.end(); ++it) {
file->removeBudget(*it);
}
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to remove budget: %1, thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
void KMyMoneyApp::slotBudgetCopy()
{
if (d->m_selectedBudgets.size() == 1) {
MyMoneyFileTransaction ft;
try {
MyMoneyBudget budget = d->m_selectedBudgets.first();
budget.clearId();
budget.setName(i18n("Copy of %1", budget.name()));
MyMoneyFile::instance()->addBudget(budget);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to add budget: %1, thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
}
void KMyMoneyApp::slotBudgetChangeYear()
{
if (d->m_selectedBudgets.size() == 1) {
QStringList years;
int current = 0;
bool haveCurrent = false;
MyMoneyBudget budget = *(d->m_selectedBudgets.begin());
for (int i = (QDate::currentDate().year() - 3); i < (QDate::currentDate().year() + 5); ++i) {
years << QString("%1").arg(i);
if (i == budget.budgetStart().year()) {
haveCurrent = true;
}
if (!haveCurrent)
++current;
}
if (!haveCurrent)
current = 0;
bool ok = false;
QString yearString = QInputDialog::getItem(this, i18n("Select year"), i18n("Budget year"), years, current, false, &ok);
if (ok) {
int year = yearString.toInt(0, 0);
QDate newYear = QDate(year, budget.budgetStart().month(), budget.budgetStart().day());
if (newYear != budget.budgetStart()) {
MyMoneyFileTransaction ft;
try {
budget.setBudgetStart(newYear);
MyMoneyFile::instance()->modifyBudget(budget);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify budget: %1, thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
}
}
}
void KMyMoneyApp::slotBudgetForecast()
{
if (d->m_selectedBudgets.size() == 1) {
MyMoneyFileTransaction ft;
try {
MyMoneyBudget budget = d->m_selectedBudgets.first();
bool calcBudget = budget.getaccounts().count() == 0;
if (!calcBudget) {
if (KMessageBox::warningContinueCancel(0, i18n("The current budget already contains data. Continuing will replace all current values of this budget."), i18nc("Warning message box", "Warning")) == KMessageBox::Continue)
calcBudget = true;
}
if (calcBudget) {
QDate historyStart;
QDate historyEnd;
QDate budgetStart;
QDate budgetEnd;
budgetStart = budget.budgetStart();
budgetEnd = budgetStart.addYears(1).addDays(-1);
historyStart = budgetStart.addYears(-1);
historyEnd = budgetEnd.addYears(-1);
MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast();
forecast.createBudget(budget, historyStart, historyEnd, budgetStart, budgetEnd, true);
MyMoneyFile::instance()->modifyBudget(budget);
ft.commit();
}
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify budget: %1, thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
}
void KMyMoneyApp::slotNewFeature()
{
}
void KMyMoneyApp::slotTransactionsDelete()
{
// since we may jump here via code, we have to make sure to react only
// if the action is enabled
if (!kmymoney->actionCollection()->action(s_Actions[Action::TransactionDelete])->isEnabled())
return;
if (d->m_selectedTransactions.isEmpty())
return;
if (d->m_selectedTransactions.warnLevel() == 1) {
if (KMessageBox::warningContinueCancel(0,
i18n("At least one split of the selected transactions has been reconciled. "
"Do you wish to delete the transactions anyway?"),
i18n("Transaction already reconciled")) == KMessageBox::Cancel)
return;
}
QString msg =
i18np("Do you really want to delete the selected transaction?",
"Do you really want to delete all %1 selected transactions?",
d->m_selectedTransactions.count());
if (KMessageBox::questionYesNo(this, msg, i18n("Delete transaction")) == KMessageBox::Yes) {
KMSTATUS(i18n("Deleting transactions"));
doDeleteTransactions();
}
}
void KMyMoneyApp::slotTransactionDuplicate()
{
// since we may jump here via code, we have to make sure to react only
// if the action is enabled
if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionDuplicate])->isEnabled()) {
KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions;
KMyMoneyRegister::SelectedTransactions::iterator it_t;
int i = 0;
int cnt = d->m_selectedTransactions.count();
KMSTATUS(i18n("Duplicating transactions"));
slotStatusProgressBar(0, cnt);
MyMoneyFileTransaction ft;
MyMoneyTransaction lt;
try {
for (it_t = list.begin(); it_t != list.end(); ++it_t) {
MyMoneyTransaction t = (*it_t).transaction();
QList<MyMoneySplit>::iterator it_s;
// wipe out any reconciliation information
for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
(*it_s).setReconcileFlag(MyMoneySplit::NotReconciled);
(*it_s).setReconcileDate(QDate());
(*it_s).setBankID(QString());
}
// clear invalid data
t.setEntryDate(QDate());
t.clearId();
// and set the post date to today
t.setPostDate(QDate::currentDate());
MyMoneyFile::instance()->addTransaction(t);
lt = t;
slotStatusProgressBar(i++, 0);
}
ft.commit();
// select the new transaction in the ledger
if (!d->m_selectedAccount.id().isEmpty())
d->m_myMoneyView->slotLedgerSelected(d->m_selectedAccount.id(), lt.id());
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to duplicate transaction(s): %1, thrown in %2:%3", e.what(), e.file(), e.line()));
}
// switch off the progress bar
slotStatusProgressBar(-1, -1);
}
}
void KMyMoneyApp::doDeleteTransactions()
{
KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions;
KMyMoneyRegister::SelectedTransactions::iterator it_t;
int cnt = list.count();
int i = 0;
slotStatusProgressBar(0, cnt);
MyMoneyFileTransaction ft;
MyMoneyFile* file = MyMoneyFile::instance();
try {
it_t = list.begin();
while (it_t != list.end()) {
// only remove those transactions that do not reference a closed account
if (!file->referencesClosedAccount((*it_t).transaction())) {
file->removeTransaction((*it_t).transaction());
// remove all those references in the list of selected transactions
// that refer to the same transaction we just removed so that we
// will not be caught by an exception later on (see bko #285310)
KMyMoneyRegister::SelectedTransactions::iterator it_td = it_t;
++it_td;
while (it_td != list.end()) {
if ((*it_t).transaction().id() == (*it_td).transaction().id()) {
it_td = list.erase(it_td);
i++; // bump count of deleted transactions
} else {
++it_td;
}
}
}
// need to ensure "nextCheckNumber" is still correct
MyMoneyAccount acc = file->account((*it_t).split().accountId());
// the "lastNumberUsed" might have been the txn number deleted
// so adjust it
QString deletedNum = (*it_t).split().number();
// decrement deletedNum and set new "lastNumberUsed"
QString num = KMyMoneyUtils::getAdjacentNumber(deletedNum, -1);
acc.setValue("lastNumberUsed", num);
file->modifyAccount(acc);
list.erase(it_t);
it_t = list.begin();
slotStatusProgressBar(i++, 0);
}
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to delete transaction(s): %1, thrown in %2:%3", e.what(), e.file(), e.line()));
}
slotStatusProgressBar(-1, -1);
}
void KMyMoneyApp::slotTransactionsNew()
{
// since we jump here via code, we have to make sure to react only
// if the action is enabled
if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionNew])->isEnabled()) {
if (d->m_myMoneyView->createNewTransaction()) {
d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions);
if (d->m_transactionEditor) {
KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart());
KMyMoneyPayeeCombo* payeeEdit = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_transactionEditor->haveWidget("payee"));
if (payeeEdit && !d->m_lastPayeeEnteredId.isEmpty()) {
// in case we entered a new transaction before and used a payee,
// we reuse it here. Save the text to the edit widget, select it
// so that hitting any character will start entering another payee.
payeeEdit->setSelectedItem(d->m_lastPayeeEnteredId);
payeeEdit->lineEdit()->selectAll();
}
if (d->m_transactionEditor) {
connect(d->m_transactionEditor, SIGNAL(statusProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int)));
connect(d->m_transactionEditor, SIGNAL(statusMsg(QString)), this, SLOT(slotStatusMsg(QString)));
- connect(d->m_transactionEditor, SIGNAL(scheduleTransaction(MyMoneyTransaction,MyMoneySchedule::occurrenceE)), this, SLOT(slotScheduleNew(MyMoneyTransaction,MyMoneySchedule::occurrenceE)));
+ connect(d->m_transactionEditor, SIGNAL(scheduleTransaction(MyMoneyTransaction,eMyMoney::Schedule::Occurrence)), this, SLOT(slotScheduleNew(MyMoneyTransaction,eMyMoney::Schedule::Occurrence)));
}
slotUpdateActions();
}
}
}
}
void KMyMoneyApp::slotTransactionsEdit()
{
// qDebug("KMyMoneyApp::slotTransactionsEdit()");
// since we jump here via code, we have to make sure to react only
// if the action is enabled
if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionEdit])->isEnabled()) {
// as soon as we edit a transaction, we don't remember the last payee entered
d->m_lastPayeeEnteredId.clear();
d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions);
KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart());
slotUpdateActions();
}
}
void KMyMoneyApp::deleteTransactionEditor()
{
// make sure, we don't use the transaction editor pointer
// anymore from now on
TransactionEditor* p = d->m_transactionEditor;
d->m_transactionEditor = 0;
delete p;
}
void KMyMoneyApp::slotTransactionsEditSplits()
{
// since we jump here via code, we have to make sure to react only
// if the action is enabled
if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionEditSplits])->isEnabled()) {
// as soon as we edit a transaction, we don't remember the last payee entered
d->m_lastPayeeEnteredId.clear();
d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions);
slotUpdateActions();
if (d->m_transactionEditor) {
KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart());
if (d->m_transactionEditor->slotEditSplits() == QDialog::Accepted) {
MyMoneyFileTransaction ft;
try {
QString id;
connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString)));
d->m_transactionEditor->enterTransactions(id);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
}
deleteTransactionEditor();
slotUpdateActions();
}
}
void KMyMoneyApp::slotTransactionsCancel()
{
// since we jump here via code, we have to make sure to react only
// if the action is enabled
if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionCancel])->isEnabled()) {
// make sure, we block the enter function
actionCollection()->action(s_Actions[Action::TransactionEnter])->setEnabled(false);
// qDebug("KMyMoneyApp::slotTransactionsCancel");
deleteTransactionEditor();
slotUpdateActions();
}
}
void KMyMoneyApp::slotTransactionsEnter()
{
// since we jump here via code, we have to make sure to react only
// if the action is enabled
if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->isEnabled()) {
// disable the action while we process it to make sure it's processed only once since
// d->m_transactionEditor->enterTransactions(newId) will run QCoreApplication::processEvents
// we could end up here twice which will cause a crash slotUpdateActions() will enable the action again
kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->setEnabled(false);
if (d->m_transactionEditor) {
QString accountId = d->m_selectedAccount.id();
QString newId;
connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString)));
if (d->m_transactionEditor->enterTransactions(newId)) {
KMyMoneyPayeeCombo* payeeEdit = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_transactionEditor->haveWidget("payee"));
if (payeeEdit && !newId.isEmpty()) {
d->m_lastPayeeEnteredId = payeeEdit->selectedItem();
}
deleteTransactionEditor();
}
if (!newId.isEmpty()) {
d->m_myMoneyView->slotLedgerSelected(accountId, newId);
}
}
slotUpdateActions();
}
}
void KMyMoneyApp::slotTransactionsCancelOrEnter(bool& okToSelect)
{
static bool oneTime = false;
if (!oneTime) {
oneTime = true;
QString dontShowAgain = "CancelOrEditTransaction";
// qDebug("KMyMoneyApp::slotCancelOrEndEdit");
if (d->m_transactionEditor) {
if (KMyMoneyGlobalSettings::focusChangeIsEnter() && kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->isEnabled()) {
slotTransactionsEnter();
if (d->m_transactionEditor) {
// if at this stage the editor is still there that means that entering the transaction was cancelled
// for example by pressing cancel on the exchange rate editor so we must stay in edit mode
okToSelect = false;
}
} else {
// okToSelect is preset to true if a cancel of the dialog is useful and false if it is not
int rc;
KGuiItem noGuiItem = KStandardGuiItem::save();
KGuiItem yesGuiItem = KStandardGuiItem::discard();
KGuiItem cancelGuiItem = KStandardGuiItem::cont();
// if the transaction can't be entered make sure that it can't be entered by pressing no either
if (!kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->isEnabled()) {
noGuiItem.setEnabled(false);
noGuiItem.setToolTip(kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->toolTip());
}
if (okToSelect == true) {
rc = KMessageBox::warningYesNoCancel(0, i18n("<p>Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.</p><p>You can also set an option to save the transaction automatically when e.g. selecting another transaction.</p>"), i18n("End transaction edit"), yesGuiItem, noGuiItem, cancelGuiItem, dontShowAgain);
} else {
rc = KMessageBox::warningYesNo(0, i18n("<p>Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.</p><p>You can also set an option to save the transaction automatically when e.g. selecting another transaction.</p>"), i18n("End transaction edit"), yesGuiItem, noGuiItem, dontShowAgain);
}
switch (rc) {
case KMessageBox::Yes:
slotTransactionsCancel();
break;
case KMessageBox::No:
slotTransactionsEnter();
// make sure that we'll see this message the next time no matter
// if the user has chosen the 'Don't show again' checkbox
KMessageBox::enableMessage(dontShowAgain);
if (d->m_transactionEditor) {
// if at this stage the editor is still there that means that entering the transaction was cancelled
// for example by pressing cancel on the exchange rate editor so we must stay in edit mode
okToSelect = false;
}
break;
case KMessageBox::Cancel:
// make sure that we'll see this message the next time no matter
// if the user has chosen the 'Don't show again' checkbox
KMessageBox::enableMessage(dontShowAgain);
okToSelect = false;
break;
}
}
}
oneTime = false;
}
}
void KMyMoneyApp::slotToggleReconciliationFlag()
{
markTransaction(MyMoneySplit::Unknown);
}
void KMyMoneyApp::slotMarkTransactionCleared()
{
markTransaction(MyMoneySplit::Cleared);
}
void KMyMoneyApp::slotMarkTransactionReconciled()
{
markTransaction(MyMoneySplit::Reconciled);
}
void KMyMoneyApp::slotMarkTransactionNotReconciled()
{
markTransaction(MyMoneySplit::NotReconciled);
}
void KMyMoneyApp::markTransaction(MyMoneySplit::reconcileFlagE flag)
{
KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions;
KMyMoneyRegister::SelectedTransactions::const_iterator it_t;
int cnt = list.count();
int i = 0;
slotStatusProgressBar(0, cnt);
MyMoneyFileTransaction ft;
try {
for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) {
// turn on signals before we modify the last entry in the list
cnt--;
MyMoneyFile::instance()->blockSignals(cnt != 0);
// get a fresh copy
MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id());
MyMoneySplit sp = t.splitById((*it_t).split().id());
if (sp.reconcileFlag() != flag) {
if (flag == MyMoneySplit::Unknown) {
if (d->m_reconciliationAccount.id().isEmpty()) {
// in normal mode we cycle through all states
switch (sp.reconcileFlag()) {
case MyMoneySplit::NotReconciled:
sp.setReconcileFlag(MyMoneySplit::Cleared);
break;
case MyMoneySplit::Cleared:
sp.setReconcileFlag(MyMoneySplit::Reconciled);
break;
case MyMoneySplit::Reconciled:
sp.setReconcileFlag(MyMoneySplit::NotReconciled);
break;
default:
break;
}
} else {
// in reconciliation mode we skip the reconciled state
switch (sp.reconcileFlag()) {
case MyMoneySplit::NotReconciled:
sp.setReconcileFlag(MyMoneySplit::Cleared);
break;
case MyMoneySplit::Cleared:
sp.setReconcileFlag(MyMoneySplit::NotReconciled);
break;
default:
break;
}
}
} else {
sp.setReconcileFlag(flag);
}
t.modifySplit(sp);
MyMoneyFile::instance()->modifyTransaction(t);
}
slotStatusProgressBar(i++, 0);
}
slotStatusProgressBar(-1, -1);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
void KMyMoneyApp::slotTransactionsAccept()
{
KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions;
KMyMoneyRegister::SelectedTransactions::const_iterator it_t;
int cnt = list.count();
int i = 0;
slotStatusProgressBar(0, cnt);
MyMoneyFileTransaction ft;
try {
for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) {
// reload transaction in case it got changed during the course of this loop
MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id());
if (t.isImported()) {
t.setImported(false);
if (!d->m_selectedAccount.id().isEmpty()) {
QList<MyMoneySplit> list = t.splits();
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) {
if ((*it_s).accountId() == d->m_selectedAccount.id()) {
if ((*it_s).reconcileFlag() == MyMoneySplit::NotReconciled) {
MyMoneySplit s = (*it_s);
s.setReconcileFlag(MyMoneySplit::Cleared);
t.modifySplit(s);
}
}
}
}
MyMoneyFile::instance()->modifyTransaction(t);
}
if ((*it_t).split().isMatched()) {
// reload split in case it got changed during the course of this loop
MyMoneySplit s = t.splitById((*it_t).split().id());
TransactionMatcher matcher(d->m_selectedAccount);
matcher.accept(t, s);
}
slotStatusProgressBar(i++, 0);
}
slotStatusProgressBar(-1, -1);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to accept transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
void KMyMoneyApp::slotTransactionGotoAccount()
{
if (!d->m_accountGoto.isEmpty()) {
try {
QString transactionId;
if (d->m_selectedTransactions.count() == 1) {
transactionId = d->m_selectedTransactions[0].transaction().id();
}
// make sure to pass a copy, as d->myMoneyView->slotLedgerSelected() overrides
// d->m_accountGoto while calling slotUpdateActions()
QString accountId = d->m_accountGoto;
d->m_myMoneyView->slotLedgerSelected(accountId, transactionId);
} catch (const MyMoneyException &) {
}
}
}
void KMyMoneyApp::slotTransactionGotoPayee()
{
if (!d->m_payeeGoto.isEmpty()) {
try {
QString transactionId;
if (d->m_selectedTransactions.count() == 1) {
transactionId = d->m_selectedTransactions[0].transaction().id();
}
// make sure to pass copies, as d->myMoneyView->slotPayeeSelected() overrides
// d->m_payeeGoto and d->m_selectedAccount while calling slotUpdateActions()
QString payeeId = d->m_payeeGoto;
QString accountId = d->m_selectedAccount.id();
d->m_myMoneyView->slotPayeeSelected(payeeId, accountId, transactionId);
} catch (const MyMoneyException &) {
}
}
}
void KMyMoneyApp::slotTransactionCreateSchedule()
{
if (d->m_selectedTransactions.count() == 1) {
// make sure to have the current selected split as first split in the schedule
MyMoneyTransaction t = d->m_selectedTransactions[0].transaction();
MyMoneySplit s = d->m_selectedTransactions[0].split();
QString splitId = s.id();
s.clearId();
s.setReconcileFlag(MyMoneySplit::NotReconciled);
s.setReconcileDate(QDate());
t.removeSplits();
t.addSplit(s);
const QList<MyMoneySplit>& splits = d->m_selectedTransactions[0].transaction().splits();
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = splits.begin(); it_s != splits.end(); ++it_s) {
if ((*it_s).id() != splitId) {
MyMoneySplit s0 = (*it_s);
s0.clearId();
s0.setReconcileFlag(MyMoneySplit::NotReconciled);
s0.setReconcileDate(QDate());
t.addSplit(s0);
}
}
slotScheduleNew(t);
}
}
void KMyMoneyApp::slotTransactionAssignNumber()
{
if (d->m_transactionEditor)
d->m_transactionEditor->assignNextNumber();
}
void KMyMoneyApp::slotTransactionCombine()
{
qDebug("slotTransactionCombine() not implemented yet");
}
void KMyMoneyApp::slotTransactionCopySplits()
{
MyMoneyFile* file = MyMoneyFile::instance();
if (d->m_selectedTransactions.count() >= 2) {
int singleSplitTransactions = 0;
int multipleSplitTransactions = 0;
KMyMoneyRegister::SelectedTransaction selectedSourceTransaction;
foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) {
switch (st.transaction().splitCount()) {
case 0:
break;
case 1:
singleSplitTransactions++;
break;
default:
selectedSourceTransaction = st;
multipleSplitTransactions++;
break;
}
}
if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) {
MyMoneyFileTransaction ft;
try {
const MyMoneyTransaction& sourceTransaction = selectedSourceTransaction.transaction();
const MyMoneySplit& sourceSplit = selectedSourceTransaction.split();
foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) {
MyMoneyTransaction t = st.transaction();
// don't process the source transaction
if (sourceTransaction.id() == t.id()) {
continue;
}
const MyMoneySplit& baseSplit = st.split();
if (t.splitCount() == 1) {
foreach (const MyMoneySplit& split, sourceTransaction.splits()) {
// Don't copy the source split, as we already have that
// as part of the destination transaction
if (split.id() == sourceSplit.id()) {
continue;
}
MyMoneySplit sp(split);
// clear the ID and reconciliation state
sp.clearId();
sp.setReconcileFlag(MyMoneySplit::NotReconciled);
sp.setReconcileDate(QDate());
// in case it is a simple transaction consisting of two splits,
// we can adjust the share and value part of the second split we
// just created. We need to keep a possible price in mind in case
// of different currencies
if (sourceTransaction.splitCount() == 2) {
sp.setValue(-baseSplit.value());
sp.setShares(-(baseSplit.shares() * baseSplit.price()));
}
t.addSplit(sp);
}
file->modifyTransaction(t);
}
}
ft.commit();
} catch (const MyMoneyException &) {
qDebug() << "transactionCopySplits() failed";
}
}
}
}
void KMyMoneyApp::slotMoveToAccount(const QString& id)
{
// close the menu, if it is still open
QWidget* w = factory()->container("transaction_context_menu", this);
if (w && w->isVisible()) {
w->close();
}
if (!d->m_selectedTransactions.isEmpty()) {
MyMoneyFileTransaction ft;
try {
KMyMoneyRegister::SelectedTransactions::const_iterator it_t;
for (it_t = d->m_selectedTransactions.constBegin(); it_t != d->m_selectedTransactions.constEnd(); ++it_t) {
- if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) {
+ if (d->m_selectedAccount.accountType() == eMyMoney::Account::Investment) {
d->moveInvestmentTransaction(d->m_selectedAccount.id(), id, (*it_t).transaction());
} else {
QList<MyMoneySplit>::const_iterator it_s;
bool changed = false;
MyMoneyTransaction t = (*it_t).transaction();
for (it_s = (*it_t).transaction().splits().constBegin(); it_s != (*it_t).transaction().splits().constEnd(); ++it_s) {
if ((*it_s).accountId() == d->m_selectedAccount.id()) {
MyMoneySplit s = (*it_s);
s.setAccountId(id);
t.modifySplit(s);
changed = true;
}
}
if (changed) {
MyMoneyFile::instance()->modifyTransaction(t);
}
}
}
ft.commit();
} catch (const MyMoneyException &) {
}
}
}
// 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;
for (QList<MyMoneySplit>::const_iterator it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) {
stockAccountId = (*it_s).accountId();
stockSecurityId =
MyMoneyFile::instance()->account(stockAccountId).currencyId();
if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) {
s = *it_s;
break;
}
}
// Now check the target investment account to see if it
// contains a stock with this id
QString newStockAccountId;
QStringList accountList = toInvAcc.accountList();
for (QStringList::const_iterator it_a = accountList.constBegin(); it_a != accountList.constEnd(); ++it_a) {
if (MyMoneyFile::instance()->account((*it_a)).currencyId() ==
stockSecurityId) {
newStockAccountId = (*it_a);
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::slotUpdateMoveToAccountMenu()
{
createTransactionMoveMenu();
// in case we were not able to create the selector, we
// better get out of here. Anything else would cause
// a crash later on (accountSet.load)
if (!d->m_moveToAccountSelector)
return;
if (!d->m_selectedAccount.id().isEmpty()) {
AccountSet accountSet;
- if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) {
- accountSet.addAccountType(MyMoneyAccount::Investment);
+ if (d->m_selectedAccount.accountType() == eMyMoney::Account::Investment) {
+ accountSet.addAccountType(eMyMoney::Account::Investment);
} else if (d->m_selectedAccount.isAssetLiability()) {
- accountSet.addAccountType(MyMoneyAccount::Checkings);
- accountSet.addAccountType(MyMoneyAccount::Savings);
- accountSet.addAccountType(MyMoneyAccount::Cash);
- accountSet.addAccountType(MyMoneyAccount::AssetLoan);
- accountSet.addAccountType(MyMoneyAccount::CertificateDep);
- accountSet.addAccountType(MyMoneyAccount::MoneyMarket);
- accountSet.addAccountType(MyMoneyAccount::Asset);
- accountSet.addAccountType(MyMoneyAccount::Currency);
- accountSet.addAccountType(MyMoneyAccount::CreditCard);
- accountSet.addAccountType(MyMoneyAccount::Loan);
- accountSet.addAccountType(MyMoneyAccount::Liability);
+ accountSet.addAccountType(eMyMoney::Account::Checkings);
+ accountSet.addAccountType(eMyMoney::Account::Savings);
+ accountSet.addAccountType(eMyMoney::Account::Cash);
+ accountSet.addAccountType(eMyMoney::Account::AssetLoan);
+ accountSet.addAccountType(eMyMoney::Account::CertificateDep);
+ accountSet.addAccountType(eMyMoney::Account::MoneyMarket);
+ accountSet.addAccountType(eMyMoney::Account::Asset);
+ accountSet.addAccountType(eMyMoney::Account::Currency);
+ accountSet.addAccountType(eMyMoney::Account::CreditCard);
+ accountSet.addAccountType(eMyMoney::Account::Loan);
+ accountSet.addAccountType(eMyMoney::Account::Liability);
} else if (d->m_selectedAccount.isIncomeExpense()) {
- accountSet.addAccountType(MyMoneyAccount::Income);
- accountSet.addAccountType(MyMoneyAccount::Expense);
+ accountSet.addAccountType(eMyMoney::Account::Income);
+ accountSet.addAccountType(eMyMoney::Account::Expense);
}
accountSet.load(d->m_moveToAccountSelector);
// remove those accounts that we currently reference
KMyMoneyRegister::SelectedTransactions::const_iterator it_t;
for (it_t = d->m_selectedTransactions.constBegin(); it_t != d->m_selectedTransactions.constEnd(); ++it_t) {
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = (*it_t).transaction().splits().constBegin(); it_s != (*it_t).transaction().splits().constEnd(); ++it_s) {
d->m_moveToAccountSelector->removeItem((*it_s).accountId());
}
}
// remove those accounts from the list that are denominated
// in a different currency
QStringList list = d->m_moveToAccountSelector->accountList();
QList<QString>::const_iterator it_a;
for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a);
if (acc.currencyId() != d->m_selectedAccount.currencyId())
d->m_moveToAccountSelector->removeItem((*it_a));
}
}
}
void KMyMoneyApp::slotTransactionMatch()
{
// if the menu action is retrieved it can contain an '&' character for the accelerator causing the comparison to fail if not removed
QString transactionActionText = actionCollection()->action(s_Actions[Action::TransactionMatch])->text();
transactionActionText.remove('&');
if (transactionActionText == i18nc("Button text for match transaction", "Match"))
transactionMatch();
else
transactionUnmatch();
}
void KMyMoneyApp::transactionUnmatch()
{
KMyMoneyRegister::SelectedTransactions::const_iterator it;
MyMoneyFileTransaction ft;
try {
for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) {
if ((*it).split().isMatched()) {
TransactionMatcher matcher(d->m_selectedAccount);
matcher.unmatch((*it).transaction(), (*it).split());
}
}
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to unmatch the selected transactions"), e.what());
}
}
void KMyMoneyApp::transactionMatch()
{
if (d->m_selectedTransactions.count() != 2)
return;
MyMoneyTransaction startMatchTransaction;
MyMoneyTransaction endMatchTransaction;
MyMoneySplit startSplit;
MyMoneySplit endSplit;
KMyMoneyRegister::SelectedTransactions::const_iterator it;
KMyMoneyRegister::SelectedTransactions toBeDeleted;
for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) {
if ((*it).transaction().isImported()) {
if (endMatchTransaction.id().isEmpty()) {
endMatchTransaction = (*it).transaction();
endSplit = (*it).split();
toBeDeleted << *it;
} else {
//This is a second imported transaction, we still want to merge
startMatchTransaction = (*it).transaction();
startSplit = (*it).split();
}
} else if (!(*it).split().isMatched()) {
if (startMatchTransaction.id().isEmpty()) {
startMatchTransaction = (*it).transaction();
startSplit = (*it).split();
} else {
endMatchTransaction = (*it).transaction();
endSplit = (*it).split();
toBeDeleted << *it;
}
}
}
#if 0
KMergeTransactionsDlg dlg(d->m_selectedAccount);
dlg.addTransaction(startMatchTransaction);
dlg.addTransaction(endMatchTransaction);
if (dlg.exec() == QDialog::Accepted)
#endif
{
MyMoneyFileTransaction ft;
try {
if (startMatchTransaction.id().isEmpty())
throw MYMONEYEXCEPTION(i18n("No manually entered transaction selected for matching"));
if (endMatchTransaction.id().isEmpty())
throw MYMONEYEXCEPTION(i18n("No imported transaction selected for matching"));
TransactionMatcher matcher(d->m_selectedAccount);
matcher.match(startMatchTransaction, startSplit, endMatchTransaction, endSplit, true);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to match the selected transactions"), e.what());
}
}
}
void KMyMoneyApp::showContextMenu(const QString& containerName)
{
QWidget* w = factory()->container(containerName, this);
QMenu *menu = dynamic_cast<QMenu*>(w);
if (menu)
menu->exec(QCursor::pos());
else
qDebug("menu '%s' not found: w = %p, menu = %p", qPrintable(containerName), w, menu);
}
void KMyMoneyApp::slotShowTransactionContextMenu()
{
if (d->m_selectedTransactions.isEmpty() && d->m_selectedSchedule != MyMoneySchedule()) {
showContextMenu("schedule_context_menu");
} else {
showContextMenu("transaction_context_menu");
}
}
void KMyMoneyApp::slotShowInvestmentContextMenu()
{
showContextMenu("investment_context_menu");
}
void KMyMoneyApp::slotShowScheduleContextMenu()
{
showContextMenu("schedule_context_menu");
}
void KMyMoneyApp::slotShowAccountContextMenu(const MyMoneyObject& obj)
{
// qDebug("KMyMoneyApp::slotShowAccountContextMenu");
if (typeid(obj) != typeid(MyMoneyAccount))
return;
const MyMoneyAccount& acc = dynamic_cast<const MyMoneyAccount&>(obj);
// if the selected account is actually a stock account, we
// call the right slot instead
if (acc.isInvest()) {
showContextMenu("investment_context_menu");
} else if (acc.isIncomeExpense()) {
showContextMenu("category_context_menu");
} else {
showContextMenu("account_context_menu");
}
}
void KMyMoneyApp::slotShowInstitutionContextMenu(const MyMoneyObject& obj)
{
if (typeid(obj) != typeid(MyMoneyInstitution))
return;
showContextMenu("institution_context_menu");
}
void KMyMoneyApp::slotShowPayeeContextMenu()
{
showContextMenu("payee_context_menu");
}
void KMyMoneyApp::slotShowTagContextMenu()
{
showContextMenu("tag_context_menu");
}
void KMyMoneyApp::slotShowBudgetContextMenu()
{
showContextMenu("budget_context_menu");
}
void KMyMoneyApp::slotShowCurrencyContextMenu()
{
showContextMenu("currency_context_menu");
}
void KMyMoneyApp::slotShowPriceContextMenu()
{
showContextMenu("price_context_menu");
}
void KMyMoneyApp::slotShowOnlineJobContextMenu()
{
showContextMenu("onlinejob_context_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()
{
MyMoneyFile* file = MyMoneyFile::instance();
const bool fileOpen = d->m_myMoneyView->fileOpen();
const bool modified = file->dirty();
const bool importRunning = (d->m_smtReader != 0);
QWidget* w;
KActionCollection *aC = actionCollection();
// *************
// Disabling actions to be disabled at this point
// *************
{
static const QVector<Action> disabledActions {
Action::AccountStartReconciliation, Action::AccountFinishReconciliation, Action::AccountPostponeReconciliation,
Action::AccountEdit, Action::AccountDelete, Action::AccountOpen, Action::AccountClose, Action::AccountReopen,
Action::AccountTransactionReport, Action::AccountOnlineMap, Action::AccountOnlineUnmap,
Action::AccountUpdate, Action::AccountUpdateAll, Action::AccountBalanceChart,
Action::CategoryEdit, Action::CategoryDelete, Action::InstitutionEdit, Action::InstitutionDelete,
Action::InvestmentEdit, Action::InvestmentNew, Action::InvestmentDelete, Action::InvestmentOnlinePrice, Action::InvestmentManualPrice,
Action::ScheduleEdit, Action::ScheduleDelete, Action::ScheduleEnter, Action::ScheduleSkip,
Action::PayeeDelete, Action::PayeeRename, Action::PayeeMerge, Action::TagDelete, Action::TagRename,
Action::BudgetDelete, Action::BudgetRename, Action::BudgetChangeYear, Action::BudgetNew, Action::BudgetCopy, Action::BudgetForecast,
Action::TransactionEdit, Action::TransactionEditSplits, Action::TransactionEnter,
Action::TransactionCancel, Action::TransactionDelete, Action::TransactionMatch,
Action::TransactionAccept, Action::TransactionDuplicate, Action::TransactionToggleReconciled, Action::TransactionToggleCleared,
Action::TransactionGoToAccount, Action::TransactionGoToPayee, Action::TransactionAssignNumber, Action::TransactionCreateSchedule,
Action::TransactionCombine, Action::TransactionSelectAll, Action::TransactionCopySplits,
Action::ScheduleEdit, Action::ScheduleDelete, Action::ScheduleDuplicate, Action::ScheduleEnter, Action::ScheduleSkip,
Action::CurrencyRename, Action::CurrencyDelete, Action::CurrencySetBase,
Action::PriceEdit, Action::PriceDelete, Action::PriceUpdate
};
foreach (const auto a, disabledActions)
aC->action(s_Actions.value(a))->setEnabled(false);
}
// *************
// Disabling actions based on conditions
// *************
{
QString tooltip = i18n("Create a new transaction");
const QVector<QPair<Action, bool>> 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::FileImportGNC, !importRunning)},
{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::AccountNew, fileOpen)},
{qMakePair(Action::AccountCreditTransfer, onlineJobAdministration::instance()->canSendCreditTransfer())},
{qMakePair(Action::CategoryDelete, fileOpen)},
{qMakePair(Action::InstitutionNew, fileOpen)},
{qMakePair(Action::TransactionNew, (fileOpen && d->m_myMoneyView->canCreateTransactions(KMyMoneyRegister::SelectedTransactions(), tooltip)))},
{qMakePair(Action::ScheduleNew, fileOpen)},
{qMakePair(Action::CurrencyNew, fileOpen)},
{qMakePair(Action::PriceNew, fileOpen)},
};
foreach (const auto a, actionStates)
aC->action(s_Actions.value(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::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());
aC->action(s_Actions[Action::TransactionMatch])->setText(i18nc("Button text for match transaction", "Match"));
aC->action(s_Actions[Action::TransactionNew])->setToolTip(i18n("Create a new transaction"));
w = factory()->container(s_Actions[Action::TransactionMoveMenu], this);
if (w)
w->setEnabled(false);
w = factory()->container(s_Actions[Action::TransactionMarkMenu], this);
if (w)
w->setEnabled(false);
w = factory()->container(s_Actions[Action::TransactionContextMarkMenu], this);
if (w)
w->setEnabled(false);
// *************
// Enabling actions based on conditions
// *************
// FIXME for now it's always on, but we should only allow it, if we
// can select at least a single transaction
aC->action(s_Actions[Action::TransactionSelectAll])->setEnabled(true);
if (!d->m_selectedTransactions.isEmpty()) {
// enable 'delete transaction' only if at least one of the
// selected transactions does not reference a closed account
bool enable = false;
KMyMoneyRegister::SelectedTransactions::const_iterator it_t;
for (it_t = d->m_selectedTransactions.constBegin(); (enable == false) && (it_t != d->m_selectedTransactions.constEnd()); ++it_t) {
enable = !(*it_t).transaction().id().isEmpty() && !file->referencesClosedAccount((*it_t).transaction());
}
aC->action(s_Actions[Action::TransactionDelete])->setEnabled(enable);
if (!d->m_transactionEditor) {
QString tooltip = i18n("Duplicate the current selected transactions");
aC->action(s_Actions[Action::TransactionDuplicate])->setEnabled(d->m_myMoneyView->canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty());
aC->action(s_Actions[Action::TransactionDuplicate])->setToolTip(tooltip);
if (d->m_myMoneyView->canEditTransactions(d->m_selectedTransactions, tooltip)) {
aC->action(s_Actions[Action::TransactionEdit])->setEnabled(true);
// editing splits is allowed only if we have one transaction selected
if (d->m_selectedTransactions.count() == 1) {
aC->action(s_Actions[Action::TransactionEditSplits])->setEnabled(true);
}
- if (d->m_selectedAccount.isAssetLiability() && d->m_selectedAccount.accountType() != MyMoneyAccount::Investment) {
+ if (d->m_selectedAccount.isAssetLiability() && d->m_selectedAccount.accountType() != eMyMoney::Account::Investment) {
aC->action(s_Actions[Action::TransactionCreateSchedule])->setEnabled(d->m_selectedTransactions.count() == 1);
}
}
aC->action(s_Actions[Action::TransactionEdit])->setToolTip(tooltip);
if (!d->m_selectedAccount.isClosed()) {
w = factory()->container(s_Actions[Action::TransactionMoveMenu], this);
if (w)
w->setEnabled(true);
}
w = factory()->container(s_Actions[Action::TransactionMarkMenu], this);
if (w)
w->setEnabled(true);
w = factory()->container(s_Actions[Action::TransactionContextMarkMenu], this);
if (w)
w->setEnabled(true);
// Allow marking the transaction if at least one is selected
aC->action(s_Actions[Action::TransactionToggleCleared])->setEnabled(true);
aC->action(s_Actions[Action::TransactionReconciled])->setEnabled(true);
aC->action(s_Actions[Action::TransactionNotReconciled])->setEnabled(true);
aC->action(s_Actions[Action::TransactionToggleReconciled])->setEnabled(true);
if (!d->m_accountGoto.isEmpty())
aC->action(s_Actions[Action::TransactionGoToAccount])->setEnabled(true);
if (!d->m_payeeGoto.isEmpty())
aC->action(s_Actions[Action::TransactionGoToPayee])->setEnabled(true);
// Matching is enabled as soon as one regular and one imported transaction is selected
int matchedCount = 0;
int importedCount = 0;
KMyMoneyRegister::SelectedTransactions::const_iterator it;
for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) {
if ((*it).transaction().isImported())
++importedCount;
if ((*it).split().isMatched())
++matchedCount;
}
if (d->m_selectedTransactions.count() == 2 /* && aC->action(s_Actions[Action::TransactionEdit])->isEnabled() */) {
aC->action(s_Actions[Action::TransactionMatch])->setEnabled(true);
}
if (importedCount != 0 || matchedCount != 0)
aC->action(s_Actions[Action::TransactionAccept])->setEnabled(true);
if (matchedCount != 0) {
aC->action(s_Actions[Action::TransactionMatch])->setEnabled(true);
aC->action(s_Actions[Action::TransactionMatch])->setText(i18nc("Button text for unmatch transaction", "Unmatch"));
aC->action(s_Actions[Action::TransactionMatch])->setIcon(QIcon("process-stop"));
}
if (d->m_selectedTransactions.count() > 1) {
aC->action(s_Actions[Action::TransactionCombine])->setEnabled(true);
}
if (d->m_selectedTransactions.count() >= 2) {
int singleSplitTransactions = 0;
int multipleSplitTransactions = 0;
foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) {
switch (st.transaction().splitCount()) {
case 0:
break;
case 1:
singleSplitTransactions++;
break;
default:
multipleSplitTransactions++;
break;
}
}
if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) {
aC->action(s_Actions[Action::TransactionCopySplits])->setEnabled(true);
}
}
if (d->m_selectedTransactions.count() >= 2) {
int singleSplitTransactions = 0;
int multipleSplitTransactions = 0;
foreach(const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) {
switch(st.transaction().splitCount()) {
case 0:
break;
case 1:
singleSplitTransactions++;
break;
default:
multipleSplitTransactions++;
break;
}
}
if(singleSplitTransactions > 0 && multipleSplitTransactions == 1) {
aC->action(s_Actions[Action::TransactionCopySplits])->setEnabled(true);
}
}
} else {
aC->action(s_Actions[Action::TransactionAssignNumber])->setEnabled(d->m_transactionEditor->canAssignNumber());
aC->action(s_Actions[Action::TransactionNew])->setEnabled(false);
aC->action(s_Actions[Action::TransactionDelete])->setEnabled(false);
QString reason;
aC->action(s_Actions[Action::TransactionEnter])->setEnabled(d->m_transactionEditor->isComplete(reason));
//FIXME: Port to KDE4
// the next line somehow worked in KDE3 but does not have
// any influence under KDE4
/// Works for me when 'reason' is set. Allan
aC->action(s_Actions[Action::TransactionEnter])->setToolTip(reason);
aC->action(s_Actions[Action::TransactionCancel])->setEnabled(true);
}
}
QList<MyMoneyAccount> accList;
file->accountList(accList);
QList<MyMoneyAccount>::const_iterator it_a;
QMap<QString, KMyMoneyPlugin::OnlinePlugin*>::const_iterator it_p = d->m_onlinePlugins.constEnd();
for (it_a = accList.constBegin(); (it_p == d->m_onlinePlugins.constEnd()) && (it_a != accList.constEnd()); ++it_a) {
if (!(*it_a).onlineBankingSettings().value("provider").isEmpty()) {
// check if provider is available
it_p = d->m_onlinePlugins.constFind((*it_a).onlineBankingSettings().value("provider"));
if (it_p != d->m_onlinePlugins.constEnd()) {
QStringList protocols;
(*it_p)->protocols(protocols);
if (protocols.count() > 0) {
aC->action(s_Actions[Action::AccountUpdateAll])->setEnabled(true);
aC->action(s_Actions[Action::AccountUpdateMenu])->setEnabled(true);
}
}
}
}
QBitArray skip((int)eStorage::Reference::Count);
if (!d->m_selectedAccount.id().isEmpty()) {
if (!file->isStandardAccount(d->m_selectedAccount.id())) {
switch (d->m_selectedAccount.accountGroup()) {
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Equity:
+ case eMyMoney::Account::Asset:
+ case eMyMoney::Account::Liability:
+ case eMyMoney::Account::Equity:
aC->action(s_Actions[Action::AccountTransactionReport])->setEnabled(true);
aC->action(s_Actions[Action::AccountEdit])->setEnabled(true);
aC->action(s_Actions[Action::AccountDelete])->setEnabled(!file->isReferenced(d->m_selectedAccount));
aC->action(s_Actions[Action::AccountOpen])->setEnabled(true);
- if (d->m_selectedAccount.accountGroup() != MyMoneyAccount::Equity) {
+ if (d->m_selectedAccount.accountGroup() != eMyMoney::Account::Equity) {
if (d->m_reconciliationAccount.id().isEmpty()) {
aC->action(s_Actions[Action::AccountStartReconciliation])->setEnabled(true);
aC->action(s_Actions[Action::AccountStartReconciliation])->setToolTip(i18n("Reconcile"));
} else {
QString tip;
tip = i18n("Reconcile - disabled because you are currently reconciling <b>%1</b>", d->m_reconciliationAccount.name());
aC->action(s_Actions[Action::AccountStartReconciliation])->setToolTip(tip);
if (!d->m_transactionEditor) {
aC->action(s_Actions[Action::AccountFinishReconciliation])->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id());
aC->action(s_Actions[Action::AccountPostponeReconciliation])->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id());
}
}
}
- if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment)
+ if (d->m_selectedAccount.accountType() == eMyMoney::Account::Investment)
aC->action(s_Actions[Action::InvestmentNew])->setEnabled(true);
if (d->m_selectedAccount.isClosed())
aC->action(s_Actions[Action::AccountReopen])->setEnabled(true);
else enableCloseAccountAction(d->m_selectedAccount);
if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) {
aC->action(s_Actions[Action::AccountOnlineUnmap])->setEnabled(true);
// check if provider is available
QMap<QString, KMyMoneyPlugin::OnlinePlugin*>::const_iterator it_p;
it_p = d->m_onlinePlugins.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider"));
if (it_p != d->m_onlinePlugins.constEnd()) {
QStringList protocols;
(*it_p)->protocols(protocols);
if (protocols.count() > 0) {
aC->action(s_Actions[Action::AccountUpdate])->setEnabled(true);
aC->action(s_Actions[Action::AccountUpdateMenu])->setEnabled(true);
}
}
} else {
aC->action(s_Actions[Action::AccountOnlineMap])->setEnabled(d->m_onlinePlugins.count() > 0);
}
aC->action(s_Actions[Action::AccountBalanceChart])->setEnabled(true);
break;
- case MyMoneyAccount::Income :
- case MyMoneyAccount::Expense :
+ case eMyMoney::Account::Income :
+ case eMyMoney::Account::Expense :
aC->action(s_Actions[Action::CategoryEdit])->setEnabled(true);
// enable delete action, if category/account itself is not referenced
// by any object except accounts, because we want to allow
// deleting of sub-categories. Also, we allow transactions, schedules and budgets
// to be present because we can re-assign them during the delete process
skip.fill(false);
skip.setBit((int)eStorage::Reference::Transaction);
skip.setBit((int)eStorage::Reference::Account);
skip.setBit((int)eStorage::Reference::Schedule);
skip.setBit((int)eStorage::Reference::Budget);
aC->action(s_Actions[Action::CategoryDelete])->setEnabled(!file->isReferenced(d->m_selectedAccount, skip));
aC->action(s_Actions[Action::AccountOpen])->setEnabled(true);
break;
default:
break;
}
}
}
if (!d->m_selectedInstitution.id().isEmpty()) {
aC->action(s_Actions[Action::InstitutionEdit])->setEnabled(true);
aC->action(s_Actions[Action::InstitutionDelete])->setEnabled(!file->isReferenced(d->m_selectedInstitution));
}
if (!d->m_selectedInvestment.id().isEmpty()) {
aC->action(s_Actions[Action::InvestmentEdit])->setEnabled(true);
aC->action(s_Actions[Action::InvestmentDelete])->setEnabled(!file->isReferenced(d->m_selectedInvestment));
aC->action(s_Actions[Action::InvestmentManualPrice])->setEnabled(true);
try {
MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedInvestment.currencyId());
if (!security.value("kmm-online-source").isEmpty())
aC->action(s_Actions[Action::InvestmentOnlinePrice])->setEnabled(true);
} catch (const MyMoneyException &e) {
qDebug("Error retrieving security for investment %s: %s", qPrintable(d->m_selectedInvestment.name()), qPrintable(e.what()));
}
if (d->m_selectedInvestment.isClosed())
aC->action(s_Actions[Action::AccountReopen])->setEnabled(true);
else enableCloseAccountAction(d->m_selectedInvestment);
}
if (!d->m_selectedSchedule.id().isEmpty()) {
aC->action(s_Actions[Action::ScheduleEdit])->setEnabled(true);
aC->action(s_Actions[Action::ScheduleDuplicate])->setEnabled(true);
aC->action(s_Actions[Action::ScheduleDelete])->setEnabled(!file->isReferenced(d->m_selectedSchedule));
if (!d->m_selectedSchedule.isFinished()) {
aC->action(s_Actions[Action::ScheduleEnter])->setEnabled(true);
// a schedule with a single occurrence cannot be skipped
- if (d->m_selectedSchedule.occurrence() != MyMoneySchedule::OCCUR_ONCE) {
+ if (d->m_selectedSchedule.occurrence() != eMyMoney::Schedule::Occurrence::Once) {
aC->action(s_Actions[Action::ScheduleSkip])->setEnabled(true);
}
}
}
if (d->m_selectedPayees.count() >= 1) {
aC->action(s_Actions[Action::PayeeRename])->setEnabled(d->m_selectedPayees.count() == 1);
aC->action(s_Actions[Action::PayeeMerge])->setEnabled(d->m_selectedPayees.count() > 1);
aC->action(s_Actions[Action::PayeeDelete])->setEnabled(true);
}
if (d->m_selectedTags.count() >= 1) {
aC->action(s_Actions[Action::TagRename])->setEnabled(d->m_selectedTags.count() == 1);
aC->action(s_Actions[Action::TagDelete])->setEnabled(true);
}
aC->action(s_Actions[Action::BudgetNew])->setEnabled(true);
if (d->m_selectedBudgets.count() >= 1) {
aC->action(s_Actions[Action::BudgetDelete])->setEnabled(true);
if (d->m_selectedBudgets.count() == 1) {
aC->action(s_Actions[Action::BudgetChangeYear])->setEnabled(true);
aC->action(s_Actions[Action::BudgetCopy])->setEnabled(true);
aC->action(s_Actions[Action::BudgetRename])->setEnabled(true);
aC->action(s_Actions[Action::BudgetForecast])->setEnabled(true);
}
}
if (!d->m_selectedCurrency.id().isEmpty()) {
aC->action(s_Actions[Action::CurrencyRename])->setEnabled(true);
// no need to check each transaction. accounts are enough in this case
skip.fill(false);
skip.setBit((int)eStorage::Reference::Transaction);
aC->action(s_Actions[Action::CurrencyDelete])->setEnabled(!file->isReferenced(d->m_selectedCurrency, skip));
if (d->m_selectedCurrency.id() != file->baseCurrency().id())
aC->action(s_Actions[Action::CurrencySetBase])->setEnabled(true);
}
if (!d->m_selectedPrice.from().isEmpty() && d->m_selectedPrice.source() != QLatin1String("KMyMoney")) {
aC->action(s_Actions[Action::PriceEdit])->setEnabled(true);
aC->action(s_Actions[Action::PriceDelete])->setEnabled(true);
//enable online update if it is a currency
MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedPrice.from());
aC->action(s_Actions[Action::PriceUpdate])->setEnabled(security.isCurrency());
}
}
void KMyMoneyApp::slotResetSelections()
{
slotSelectAccount(MyMoneyAccount());
slotSelectInstitution(MyMoneyInstitution());
slotSelectInvestment(MyMoneyAccount());
slotSelectSchedule();
slotSelectCurrency();
slotSelectPrice();
slotSelectPayees(QList<MyMoneyPayee>());
slotSelectTags(QList<MyMoneyTag>());
slotSelectBudget(QList<MyMoneyBudget>());
slotSelectTransactions(KMyMoneyRegister::SelectedTransactions());
slotUpdateActions();
}
void KMyMoneyApp::slotSelectCurrency()
{
slotSelectCurrency(MyMoneySecurity());
}
void KMyMoneyApp::slotSelectCurrency(const MyMoneySecurity& currency)
{
d->m_selectedCurrency = currency;
slotUpdateActions();
emit currencySelected(d->m_selectedCurrency);
}
void KMyMoneyApp::slotSelectPrice()
{
slotSelectPrice(MyMoneyPrice());
}
void KMyMoneyApp::slotSelectPrice(const MyMoneyPrice& price)
{
d->m_selectedPrice = price;
slotUpdateActions();
emit priceSelected(d->m_selectedPrice);
}
void KMyMoneyApp::slotSelectBudget(const QList<MyMoneyBudget>& list)
{
d->m_selectedBudgets = list;
slotUpdateActions();
emit budgetSelected(d->m_selectedBudgets);
}
void KMyMoneyApp::slotSelectPayees(const QList<MyMoneyPayee>& list)
{
d->m_selectedPayees = list;
slotUpdateActions();
emit payeesSelected(d->m_selectedPayees);
}
void KMyMoneyApp::slotSelectTags(const QList<MyMoneyTag>& list)
{
d->m_selectedTags = list;
slotUpdateActions();
emit tagsSelected(d->m_selectedTags);
}
void KMyMoneyApp::slotSelectTransactions(const KMyMoneyRegister::SelectedTransactions& list)
{
// list can either contain a list of transactions or a single selected scheduled transaction
// in the latter case, the transaction id is actually the one of the schedule. In order
// to differentiate between the two, we just ask for the schedule. If we don't find one - because
// we passed the id of a real transaction - then we know that fact. We use the schedule here,
// because the list of schedules is kept in a cache by MyMoneyFile. This way, we save some trips
// to the backend which we would have to do if we check for the transaction.
d->m_selectedTransactions.clear();
d->m_selectedSchedule = MyMoneySchedule();
d->m_accountGoto.clear();
d->m_payeeGoto.clear();
if (!list.isEmpty() && !list.first().isScheduled()) {
d->m_selectedTransactions = list;
if (list.count() == 1) {
const MyMoneySplit& sp = d->m_selectedTransactions[0].split();
if (!sp.payeeId().isEmpty()) {
try {
MyMoneyPayee payee = MyMoneyFile::instance()->payee(sp.payeeId());
if (!payee.name().isEmpty()) {
d->m_payeeGoto = payee.id();
QString name = payee.name();
name.replace(QRegExp("&(?!&)"), "&&");
actionCollection()->action(s_Actions[Action::TransactionGoToPayee])->setText(i18n("Go to '%1'", name));
}
} catch (const MyMoneyException &) {
}
}
try {
QList<MyMoneySplit>::const_iterator it_s;
const MyMoneyTransaction& t = d->m_selectedTransactions[0].transaction();
// search the first non-income/non-expense accunt and use it for the 'goto account'
const MyMoneySplit& sp = d->m_selectedTransactions[0].split();
for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
if ((*it_s).id() != sp.id()) {
MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
if (!acc.isIncomeExpense()) {
// for stock accounts we show the portfolio account
if (acc.isInvest()) {
acc = MyMoneyFile::instance()->account(acc.parentAccountId());
}
d->m_accountGoto = acc.id();
QString name = acc.name();
name.replace(QRegExp("&(?!&)"), "&&");
actionCollection()->action(s_Actions[Action::TransactionGoToAccount])->setText(i18n("Go to '%1'", name));
break;
}
}
}
} catch (const MyMoneyException &) {
}
}
slotUpdateActions();
emit transactionsSelected(d->m_selectedTransactions);
} else if (!list.isEmpty()) {
slotSelectSchedule(MyMoneyFile::instance()->schedule(list.first().scheduleId()));
} else {
slotUpdateActions();
}
// make sure, we show some neutral menu entry if we don't have an object
if (d->m_payeeGoto.isEmpty())
actionCollection()->action(s_Actions[Action::TransactionGoToPayee])->setText(i18n("Go to payee"));
if (d->m_accountGoto.isEmpty())
actionCollection()->action(s_Actions[Action::TransactionGoToAccount])->setText(i18n("Go to account"));
}
void KMyMoneyApp::slotSelectInstitution(const MyMoneyObject& institution)
{
if (typeid(institution) != typeid(MyMoneyInstitution))
return;
d->m_selectedInstitution = dynamic_cast<const MyMoneyInstitution&>(institution);
// qDebug("slotSelectInstitution('%s')", d->m_selectedInstitution.name().data());
slotUpdateActions();
emit institutionSelected(d->m_selectedInstitution);
}
void KMyMoneyApp::slotSelectAccount(const MyMoneyObject& obj)
{
if (typeid(obj) != typeid(MyMoneyAccount))
return;
d->m_selectedAccount = MyMoneyAccount();
const MyMoneyAccount& acc = dynamic_cast<const MyMoneyAccount&>(obj);
if (!acc.isInvest())
d->m_selectedAccount = acc;
// qDebug("slotSelectAccount('%s')", d->m_selectedAccount.name().data());
slotUpdateActions();
emit accountSelected(d->m_selectedAccount);
}
void KMyMoneyApp::slotSelectInvestment(const MyMoneyObject& obj)
{
if (typeid(obj) != typeid(MyMoneyAccount))
return;
// qDebug("slotSelectInvestment('%s')", account.name().data());
d->m_selectedInvestment = MyMoneyAccount();
const MyMoneyAccount& acc = dynamic_cast<const MyMoneyAccount&>(obj);
if (acc.isInvest())
d->m_selectedInvestment = acc;
slotUpdateActions();
emit investmentSelected(d->m_selectedInvestment);
}
void KMyMoneyApp::slotSelectSchedule()
{
slotSelectSchedule(MyMoneySchedule());
}
void KMyMoneyApp::slotSelectSchedule(const MyMoneySchedule& schedule)
{
// qDebug("slotSelectSchedule('%s')", schedule.name().data());
d->m_selectedSchedule = schedule;
slotUpdateActions();
emit scheduleSelected(d->m_selectedSchedule);
}
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<KCurrencyEditDlg> dlg = new KCurrencyEditDlg(this);
connect(dlg, SIGNAL(selectObject(MyMoneySecurity)), this, SLOT(slotSelectCurrency(MyMoneySecurity)));
connect(dlg, SIGNAL(openContextMenu(MyMoneySecurity)), this, SLOT(slotShowCurrencyContextMenu()));
connect(this, SIGNAL(currencyRename()), dlg, SLOT(slotStartRename()));
connect(dlg, SIGNAL(updateCurrency(QString,QString,QString)), this, SLOT(slotCurrencyUpdate(QString,QString,QString)));
connect(this, SIGNAL(currencyCreated(QString)), dlg, SLOT(slotSelectCurrency(QString)));
connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotCurrencySetBase()));
dlg->exec();
delete dlg;
slotSelectCurrency(MyMoneySecurity());
}
void KMyMoneyApp::slotPriceDialog()
{
QPointer<KMyMoneyPriceDlg> dlg = new KMyMoneyPriceDlg(this);
connect(dlg, SIGNAL(selectObject(MyMoneyPrice)), this, SLOT(slotSelectPrice(MyMoneyPrice)));
connect(dlg, SIGNAL(openContextMenu(MyMoneyPrice)), this, SLOT(slotShowPriceContextMenu()));
connect(this, SIGNAL(priceNew()), dlg, SLOT(slotNewPrice()));
connect(this, SIGNAL(priceEdit()), dlg, SLOT(slotEditPrice()));
connect(this, SIGNAL(priceDelete()), dlg, SLOT(slotDeletePrice()));
connect(this, SIGNAL(priceOnlineUpdate()), dlg, SLOT(slotOnlinePriceUpdate()));
dlg->exec();
}
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<MyMoneySchedule> scheduleList = file->scheduleList();
QList<MyMoneySchedule>::Iterator it;
KMyMoneyUtils::EnterScheduleResultCodeE rc = KMyMoneyUtils::Enter;
for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != KMyMoneyUtils::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 != KMyMoneyUtils::Ignore
&& rc != KMyMoneyUtils::Cancel) {
rc = enterSchedule(schedule, true, true);
schedule = file->schedule((*it).id()); // get a copy of the modified schedule
}
} catch (const MyMoneyException &) {
}
}
if (rc == KMyMoneyUtils::Ignore) {
// if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction
rc = KMyMoneyUtils::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<QString> KMyMoneyApp::instanceList() const
{
QList<QString> list;
#ifdef KMM_DBUS
QDBusReply<QStringList> 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) {
#ifdef _MSC_VER
uint thisProcPid = _getpid();
#else
uint thisProcPid = getpid();
#endif
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<KEquityPriceUpdateDlg> 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 <l.lunak@suse.cz> 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<QString, KMyMoneyPlugin::ImporterPlugin*>::const_iterator it_plugin = d->m_importerPlugins.constBegin();
while (it_plugin != d->m_importerPlugins.constEnd()) {
if ((*it_plugin)->isMyFormat(url)) {
QList<MyMoneyStatement> 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_importerPlugins.constEnd())
if (MyMoneyStatement::isStatementFile(url))
slotStatementImport(url);
}
// 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, this);
KMyMoneyPlugin::pluginInterfaces().statementInterface = new KMyMoneyPlugin::KMMStatementInterface(this, this);
KMyMoneyPlugin::pluginInterfaces().viewInterface = new KMyMoneyPlugin::KMMViewInterface(this, d->m_myMoneyView, this);
// setup the calendar interface for schedules
MyMoneySchedule::setProcessingCalendar(this);
}
void KMyMoneyApp::loadPlugins()
{
Q_ASSERT(!d->m_pluginLoader);
d->m_pluginLoader = new KMyMoneyPlugin::PluginLoader(this);
//! @todo Junior Job: Improve the config read system
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup group{ config->group("Plugins") };
const auto plugins = KPluginLoader::findPlugins("kmymoney");
d->m_pluginLoader->addPluginInfo(plugins);
for (const KPluginMetaData & pluginData : plugins) {
// Only load plugins which are enabled and have the right serviceType. Other serviceTypes are loaded on demand.
if (isPluginEnabled(pluginData, group))
slotPluginLoad(pluginData);
}
connect(d->m_pluginLoader, &KMyMoneyPlugin::PluginLoader::pluginEnabled, this, &KMyMoneyApp::slotPluginLoad);
connect(d->m_pluginLoader, &KMyMoneyPlugin::PluginLoader::pluginDisabled, this, &KMyMoneyApp::slotPluginUnload);
}
void KMyMoneyApp::unloadPlugins()
{
Q_ASSERT(d->m_pluginLoader);
delete d->m_pluginLoader;
}
inline bool KMyMoneyApp::isPluginEnabled(const KPluginMetaData& metaData, const KConfigGroup& configGroup)
{
//! @fixme: there is a function in KMyMoneyPlugin::PluginLoader which has to have the same content
if (metaData.serviceTypes().contains("KMyMoney/Plugin")) {
const QString keyName{metaData.name() + "Enabled"};
if (configGroup.hasKey(keyName))
return configGroup.readEntry(keyName, true);
return metaData.isEnabledByDefault();
}
return false;
}
void KMyMoneyApp::slotPluginLoad(const KPluginMetaData& metaData)
{
std::unique_ptr<QPluginLoader> loader{new QPluginLoader{metaData.fileName()}};
QObject* plugin = loader->instance();
if (!plugin) {
qWarning("Could not load plugin '%s', error: %s", qPrintable(metaData.fileName()), qPrintable(loader->errorString()));
return;
}
if ( d->m_plugins.contains(metaData.fileName()) ) {
/** @fixme Handle a reload e.g. objectNames are equal but the object are different (plugin != d->m_plugins[plugin->objectName()])
* Also it could be usefull to drop the dependence on objectName()
*/
/* Note: there is nothing to delete here because if the plugin was loaded already,
plugin points to the same object as the previously loaded one. */
return;
}
KMyMoneyPlugin::Plugin* kmmPlugin = qobject_cast<KMyMoneyPlugin::Plugin*>(plugin);
if (!kmmPlugin) {
qWarning("Could not load plugin '%s'.", qPrintable(metaData.fileName()));
return;
}
// check for online plugin
KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast<KMyMoneyPlugin::OnlinePlugin *>(plugin);
// check for extended online plugin
KMyMoneyPlugin::OnlinePluginExtended* ope = dynamic_cast<KMyMoneyPlugin::OnlinePluginExtended*>(plugin);
// check for importer plugin
KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast<KMyMoneyPlugin::ImporterPlugin *>(plugin);
// Tell the plugin it is about to get plugged
kmmPlugin->plug();
// plug the plugin
guiFactory()->addClient(kmmPlugin);
d->m_plugins[metaData.fileName()] = kmmPlugin;
if (op)
d->m_onlinePlugins[plugin->objectName()] = op;
if (ope)
onlineJobAdministration::instance()->addPlugin(plugin->objectName(), ope);
if (ip)
d->m_importerPlugins[plugin->objectName()] = ip;
slotUpdateActions();
}
void KMyMoneyApp::slotPluginUnload(const KPluginMetaData& metaData)
{
KMyMoneyPlugin::Plugin* plugin = d->m_plugins[metaData.fileName()];
// Remove and test if the plugin was actually loaded
if (!d->m_plugins.remove(metaData.fileName()) || plugin == nullptr)
return;
// check for online plugin
KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast<KMyMoneyPlugin::OnlinePlugin *>(plugin);
// check for importer plugin
KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast<KMyMoneyPlugin::ImporterPlugin *>(plugin);
// unplug the plugin
guiFactory()->removeClient(plugin);
if (op)
d->m_onlinePlugins.remove(plugin->objectName());
if (ip)
d->m_importerPlugins.remove(plugin->objectName());
plugin->unplug();
slotUpdateActions();
}
void KMyMoneyApp::slotAutoSave()
{
if (!d->m_inAutoSaving) {
// store the focus widget so we can restore it after save
QPointer<QWidget> 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();
}
const MyMoneyAccount& KMyMoneyApp::account(const QString& key, const QString& value) const
{
QList<MyMoneyAccount> list;
QList<MyMoneyAccount>::const_iterator it_a;
MyMoneyFile::instance()->accountList(list);
QString accId;
for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
// search in the account's kvp container
const QString& accountKvpValue = (*it_a).value(key);
// search in the account's online settings kvp container
const QString& onlineSettingsKvpValue = (*it_a).onlineBankingSettings().value(key);
if (accountKvpValue.contains(value) || onlineSettingsKvpValue.contains(value)) {
if(accId.isEmpty()) {
accId = (*it_a).id();
}
}
if (accountKvpValue == value || onlineSettingsKvpValue == value) {
accId = (*it_a).id();
break;
}
}
// return the account found or an empty element
return MyMoneyFile::instance()->account(accId);
}
void KMyMoneyApp::setAccountOnlineParameters(const MyMoneyAccount& _acc, const MyMoneyKeyValueContainer& kvps)
{
MyMoneyFileTransaction ft;
try {
MyMoneyAccount acc = MyMoneyFile::instance()->account(_acc.id());
acc.setOnlineBankingSettings(kvps);
MyMoneyFile::instance()->modifyAccount(acc);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to setup online parameters for account '%1'", _acc.name()), e.what());
}
}
void KMyMoneyApp::slotAccountUnmapOnline()
{
// no account selected
if (d->m_selectedAccount.id().isEmpty())
return;
// not a mapped account
if (d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty())
return;
if (KMessageBox::warningYesNo(this, QString("<qt>%1</qt>").arg(i18n("Do you really want to remove the mapping of account <b>%1</b> 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.onlineBankingSettings().value("provider").isEmpty())
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<QString, KMyMoneyPlugin::OnlinePlugin*>::const_iterator it_p;
switch (d->m_onlinePlugins.count()) {
case 0:
break;
case 1:
provider = d->m_onlinePlugins.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_onlinePlugins.constBegin(); it_p != d->m_onlinePlugins.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_onlinePlugins.constFind(provider);
if (it_p != d->m_onlinePlugins.constEnd()) {
// plugin found, call it
MyMoneyKeyValueContainer settings;
if ((*it_p)->mapAccount(d->m_selectedAccount, settings)) {
settings["provider"] = provider;
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<MyMoneyAccount> accList;
MyMoneyFile::instance()->accountList(accList);
QList<MyMoneyAccount>::iterator it_a;
QMap<QString, KMyMoneyPlugin::OnlinePlugin*>::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).onlineBankingSettings().value("provider").isEmpty()
|| d->m_onlinePlugins.find((*it_a).onlineBankingSettings().value("provider")) == d->m_onlinePlugins.end()) {
it_a = accList.erase(it_a);
} else
++it_a;
}
QVector<Action> disabledActions {Action::AccountUpdate, Action::AccountUpdateMenu, Action::AccountUpdateAll};
foreach (const auto a, disabledActions)
actionCollection()->action(s_Actions.value(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_onlinePlugins.constFind((*it_a).onlineBankingSettings().value("provider"));
(*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.onlineBankingSettings().value("provider").isEmpty())
return;
QVector<Action> disabledActions {Action::AccountUpdate, Action::AccountUpdateMenu, Action::AccountUpdateAll};
foreach (const auto a, disabledActions)
actionCollection()->action(s_Actions.value(a))->setEnabled(false);
// find the provider
QMap<QString, KMyMoneyPlugin::OnlinePlugin*>::const_iterator it_p;
it_p = d->m_onlinePlugins.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider"));
if (it_p != d->m_onlinePlugins.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::slotNewOnlineTransfer()
{
kOnlineTransferForm *transferForm = new kOnlineTransferForm(this);
if (!d->m_selectedAccount.id().isEmpty()) {
transferForm->setCurrentAccount(d->m_selectedAccount.id());
}
connect(transferForm, SIGNAL(rejected()), transferForm, SLOT(deleteLater()));
connect(transferForm, SIGNAL(acceptedForSave(onlineJob)), this, SLOT(slotOnlineJobSave(onlineJob)));
connect(transferForm, SIGNAL(acceptedForSend(onlineJob)), this, SLOT(slotOnlineJobSend(onlineJob)));
connect(transferForm, SIGNAL(accepted()), transferForm, SLOT(deleteLater()));
transferForm->show();
}
void KMyMoneyApp::slotEditOnlineJob(const QString jobId)
{
try {
const onlineJob constJob = MyMoneyFile::instance()->getOnlineJob(jobId);
slotEditOnlineJob(constJob);
} catch (MyMoneyException&) {
// Prevent a crash in very rare cases
}
}
void KMyMoneyApp::slotEditOnlineJob(onlineJob job)
{
try {
slotEditOnlineJob(onlineJobTyped<creditTransfer>(job));
} catch (MyMoneyException&) {
}
}
void KMyMoneyApp::slotEditOnlineJob(const onlineJobTyped<creditTransfer> job)
{
kOnlineTransferForm *transferForm = new kOnlineTransferForm(this);
transferForm->setOnlineJob(job);
connect(transferForm, SIGNAL(rejected()), transferForm, SLOT(deleteLater()));
connect(transferForm, SIGNAL(acceptedForSave(onlineJob)), this, SLOT(slotOnlineJobSave(onlineJob)));
connect(transferForm, SIGNAL(acceptedForSend(onlineJob)), this, SLOT(slotOnlineJobSend(onlineJob)));
connect(transferForm, SIGNAL(accepted()), transferForm, SLOT(deleteLater()));
transferForm->show();
}
void KMyMoneyApp::slotOnlineJobSave(onlineJob job)
{
MyMoneyFileTransaction fileTransaction;
if (job.id() == MyMoneyObject::emptyId())
MyMoneyFile::instance()->addOnlineJob(job);
else
MyMoneyFile::instance()->modifyOnlineJob(job);
fileTransaction.commit();
}
/** @todo when onlineJob queue is used, continue here */
void KMyMoneyApp::slotOnlineJobSend(onlineJob job)
{
MyMoneyFileTransaction fileTransaction;
if (job.id() == MyMoneyObject::emptyId())
MyMoneyFile::instance()->addOnlineJob(job);
else
MyMoneyFile::instance()->modifyOnlineJob(job);
fileTransaction.commit();
QList<onlineJob> jobList;
jobList.append(job);
slotOnlineJobSend(jobList);
}
void KMyMoneyApp::slotOnlineJobSend(QList<onlineJob> jobs)
{
MyMoneyFile *const kmmFile = MyMoneyFile::instance();
QMultiMap<QString, onlineJob> jobsByPlugin;
// Sort jobs by online plugin & lock them
foreach (onlineJob job, jobs) {
Q_ASSERT(job.id() != MyMoneyObject::emptyId());
// find the provider
const MyMoneyAccount originAcc = job.responsibleMyMoneyAccount();
job.setLock();
job.addJobMessage(onlineJobMessage(onlineJobMessage::debug, "KMyMoneyApp::slotOnlineJobSend", "Added to queue for plugin '" + originAcc.onlineBankingSettings().value("provider") + '\''));
MyMoneyFileTransaction fileTransaction;
kmmFile->modifyOnlineJob(job);
fileTransaction.commit();
jobsByPlugin.insert(originAcc.onlineBankingSettings().value("provider"), job);
}
// Send onlineJobs to plugins
QList<QString> usedPlugins = jobsByPlugin.keys();
std::sort(usedPlugins.begin(), usedPlugins.end());
const QList<QString>::iterator newEnd = std::unique(usedPlugins.begin(), usedPlugins.end());
usedPlugins.erase(newEnd, usedPlugins.end());
foreach (const QString& pluginKey, usedPlugins) {
QMap<QString, KMyMoneyPlugin::OnlinePlugin*>::const_iterator it_p = d->m_onlinePlugins.constFind(pluginKey);
if (it_p != d->m_onlinePlugins.constEnd()) {
// plugin found, call it
KMyMoneyPlugin::OnlinePluginExtended *pluginExt = dynamic_cast< KMyMoneyPlugin::OnlinePluginExtended* >(*it_p);
if (pluginExt == 0) {
qWarning("Job given for plugin which is not an extended plugin");
continue;
}
//! @fixme remove debug message
qDebug() << "Sending " << jobsByPlugin.count(pluginKey) << " job(s) to online plugin " << pluginKey;
QList<onlineJob> jobsToExecute = jobsByPlugin.values(pluginKey);
QList<onlineJob> executedJobs = jobsToExecute;
pluginExt->sendOnlineJob(executedJobs);
// Save possible changes of the online job and remove lock
MyMoneyFileTransaction fileTransaction;
foreach (onlineJob job, executedJobs) {
fileTransaction.restart();
job.setLock(false);
kmmFile->modifyOnlineJob(job);
fileTransaction.commit();
}
if (Q_UNLIKELY(executedJobs.size() != jobsToExecute.size())) {
// OnlinePlugin did not return all jobs
qWarning() << "Error saving send online tasks. After restart you should see at minimum all successfully executed jobs marked send. Imperfect plugin: " << pluginExt->objectName();
}
} else {
qWarning() << "Error, got onlineJob for an account without online plugin.";
/** @FIXME can this actually happen? */
}
}
}
void KMyMoneyApp::slotRemoveJob()
{
}
void KMyMoneyApp::slotEditJob()
{
}
void KMyMoneyApp::slotOnlineJobLog()
{
QStringList jobIds = d->m_myMoneyView->getOnlineJobOutbox()->selectedOnlineJobs();
slotOnlineJobLog(jobIds);
}
void KMyMoneyApp::slotOnlineJobLog(const QStringList& onlineJobIds)
{
onlineJobMessagesView *const dialog = new onlineJobMessagesView();
onlineJobMessagesModel *const model = new onlineJobMessagesModel(dialog);
model->setOnlineJob(MyMoneyFile::instance()->getOnlineJob(onlineJobIds.first()));
dialog->setModel(model);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
// Note: Objects are not deleted here, Qt's parent-child system has to do that.
}
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->slotSelectInstitution(MyMoneyInstitution());
q->slotSelectInvestment(MyMoneyAccount());
q->slotSelectSchedule();
q->slotSelectCurrency();
q->slotSelectBudget(QList<MyMoneyBudget>());
q->slotSelectPayees(QList<MyMoneyPayee>());
q->slotSelectTags(QList<MyMoneyTag>());
q->slotSelectTransactions(KMyMoneyRegister::SelectedTransactions());
m_reconciliationAccount = MyMoneyAccount();
m_myMoneyView->finishReconciliation(m_reconciliationAccount);
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 138fb4296..2f7cb9d5a 100644
--- a/kmymoney/kmymoney.h
+++ b/kmymoney/kmymoney.h
@@ -1,1469 +1,1469 @@
/***************************************************************************
kmymoney.h
-------------------
copyright : (C) 2000-2001 by Michael Edwardes <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. *
* *
***************************************************************************/
#ifndef KMYMONEY_H
#define KMYMONEY_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
#include <QByteArray>
#include <QFileDialog>
#include <QUrl>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KXmlGuiWindow>
// ----------------------------------------------------------------------------
// Project Includes
#include <imymoneyprocessingcalendar.h>
#include "mymoneyaccount.h"
#include "mymoneyinstitution.h"
#include "kmymoneyutils.h"
#include "mymoney/onlinejob.h"
#include "onlinejobtyped.h"
#include "mymoneykeyvaluecontainer.h"
#include "mymoneymoney.h"
#include "mymoneyobject.h"
#include "mymoneyschedule.h"
#include "mymoneysecurity.h"
#include "mymoneysplit.h"
#include "selectedtransaction.h"
class QResizeEvent;
class KPluginMetaData;
class MyMoneyBudget;
class MyMoneyPayee;
class MyMoneyPrice;
class MyMoneyStatement;
class MyMoneyTag;
class MyMoneyTransaction;
class WebConnect;
class creditTransfer;
template <class T> class onlineJobTyped;
enum class Action {
FileOpenDatabase, FileSaveAsDatabase, FileBackup,
FileImportGNC,
FileImportStatement,
FileImportTemplate, FileExportTemplate,
#ifdef KMM_DEBUG
FileDump,
#endif
FilePersonalData, FileInformation,
EditFindTransaction,
ViewTransactionDetail, ViewHideReconciled,
ViewHideCategories, ViewShowAll,
InstitutionNew, InstitutionEdit,
InstitutionDelete,
AccountNew, AccountOpen,
AccountStartReconciliation, AccountFinishReconciliation,
AccountPostponeReconciliation, AccountEdit,
AccountDelete, AccountClose, AccountReopen,
AccountTransactionReport, AccountBalanceChart,
AccountUpdateMenu,
AccountOnlineMap, AccountOnlineUnmap,
AccountUpdate, AccountUpdateAll,
AccountCreditTransfer,
CategoryNew, CategoryEdit,
CategoryDelete,
ToolCurrencies,
ToolPrices, ToolUpdatePrices,
ToolConsistency, ToolPerformance,
ToolSQL, ToolCalculator,
SettingsAllMessages,
HelpShow,
TransactionNew, TransactionEdit,
TransactionEnter, TransactionEditSplits,
TransactionCancel, TransactionDelete,
TransactionDuplicate, TransactionMatch,
TransactionAccept, TransactionToggleReconciled,
TransactionToggleCleared, TransactionReconciled,
TransactionNotReconciled, TransactionSelectAll,
TransactionGoToAccount, TransactionGoToPayee,
TransactionCreateSchedule, TransactionAssignNumber,
TransactionCombine, TransactionCopySplits,
TransactionMoveMenu, TransactionMarkMenu,
TransactionContextMarkMenu,
InvestmentNew, InvestmentEdit,
InvestmentDelete, InvestmentOnlinePrice,
InvestmentManualPrice,
ScheduleNew, ScheduleEdit,
ScheduleDelete, ScheduleDuplicate,
ScheduleEnter, ScheduleSkip,
PayeeNew, PayeeRename, PayeeDelete,
PayeeMerge,
TagNew, TagRename, TagDelete,
BudgetNew, BudgetRename, BudgetDelete,
BudgetCopy, BudgetChangeYear, BudgetForecast,
CurrencyNew, CurrencyRename, CurrencyDelete,
CurrencySetBase,
PriceNew, PriceDelete,
PriceUpdate, PriceEdit,
#ifdef KMM_DEBUG
WizardNewUser, DebugTraces,
#endif
DebugTimers,
OnlineJobDelete, OnlineJobEdit, OnlineJobLog
};
inline uint qHash(const Action key, uint seed)
{
return ::qHash(static_cast<uint>(key), seed);
}
/*! \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 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 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
/**
* 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();
/**
* Opens a file selector dialog for the user to choose an existing OFX
* file from the file system to be imported. This slot is expected to
* be called from the UI.
*/
void slotGncImport();
/**
* Open a dialog with a chart of the balance for the currently selected
* account (m_selectedAccount). Return once the dialog is closed. Don't do
* anything if no account is selected or charts are not available.
*/
void slotAccountChart();
/**
* Opens a file selector dialog for the user to choose an existing KMM
* statement file from the file system to be imported. This is for testing
* only. KMM statement files are not designed to be exposed to the user.
*/
void slotStatementImport();
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();
/**
* Brings up the new category editor and saves the information.
* The dialog will be preset with the name. The parent defaults to
* MyMoneyFile::expense()
*
* @param name Name of the account to be created. Could include a full hierarchy
* @param id reference to storage which will receive the id after successful creation
*
* @note Typically, this slot can be connected to the
* StdTransactionEditor::createCategory(const QString&, QString&) or
* KMyMoneyCombo::createItem(const QString&, QString&) signal.
*/
void slotCategoryNew(const QString& name, QString& id);
/**
* Calls the print logic for the current view
*/
void slotPrintView();
/**
* Create a new investment
*/
void slotInvestmentNew();
/**
* Create a new investment in a given @p parent investment account
*/
void slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent);
/**
* This slot opens the investment editor to edit the currently
* selected investment if possible
*/
void slotInvestmentEdit();
/**
* Deletes the current selected investment.
*/
void slotInvestmentDelete();
/**
* Performs online update for currently selected investment
*/
void slotOnlinePriceUpdate();
/**
* Performs manual update for currently selected investment
*/
void slotManualPriceUpdate();
/**
* Call this slot, if any configuration parameter has changed
*/
void slotUpdateConfiguration();
/**
*/
bool slotPayeeNew(const QString& newnameBase, QString& id);
void slotPayeeNew();
/**
*/
void slotPayeeDelete();
/**
* Slot that merges two or more selected payess into a new payee
*/
void slotPayeeMerge();
/**
*/
void slotTagNew(const QString& newnameBase, QString& id);
void slotTagNew();
/**
*/
void slotTagDelete();
/**
*/
void slotBudgetNew();
/**
*/
void slotBudgetDelete();
/**
*/
void slotBudgetCopy();
/**
*/
void slotBudgetChangeYear();
/**
*/
void slotBudgetForecast();
/**
*/
void slotCurrencyNew();
/**
*/
void slotCurrencyUpdate(const QString &currencyId, const QString& currencyName, const QString& currencyTradingSymbol);
/**
*/
void slotCurrencyDelete();
/**
*/
void slotCurrencySetBase();
/**
* This slot is used to start new features during the development cycle
*/
void slotNewFeature();
/**
*/
void slotTransactionsNew();
/**
*/
void slotTransactionsEdit();
/**
*/
void slotTransactionsEditSplits();
/**
*/
void slotTransactionsDelete();
/**
*/
void slotTransactionsEnter();
/**
*/
void slotTransactionsCancel();
/**
*/
void slotTransactionsCancelOrEnter(bool& okToSelect);
/**
*/
void slotTransactionDuplicate();
/**
*/
void slotToggleReconciliationFlag();
/**
*/
void slotMarkTransactionCleared();
/**
*/
void slotMarkTransactionReconciled();
/**
*/
void slotMarkTransactionNotReconciled();
/**
*/
void slotTransactionGotoAccount();
/**
*/
void slotTransactionGotoPayee();
/**
*/
void slotTransactionCreateSchedule();
/**
*/
void slotTransactionAssignNumber();
/**
*/
void slotTransactionCombine();
/**
* This method takes the selected splits and checks that only one transaction (src)
* has more than one split and all others have only a single one. It then copies the
* splits of the @b src transaction to all others.
*/
void slotTransactionCopySplits();
/**
* Accept the selected transactions that are marked as 'imported' and remove the flag
*/
void slotTransactionsAccept();
/**
* 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();
void slotMoveToAccount(const QString& id);
void slotUpdateMoveToAccountMenu();
/**
* 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, MyMoneySchedule::occurrenceE occurrence = MyMoneySchedule::OCCUR_MONTHLY);
+ void slotScheduleNew(const MyMoneyTransaction& t, eMyMoney::Schedule::Occurrence occurrence = eMyMoney::Schedule::Occurrence::Monthly);
/**
*/
void slotScheduleDuplicate();
void slotAccountMapOnline();
void slotAccountUnmapOnline();
void slotAccountUpdateOnline();
void slotAccountUpdateOnlineAll();
/**
* @brief Start dialog for an online banking transfer
*/
void slotNewOnlineTransfer();
/**
* @brief Start dialog to edit onlineJob if possible
* @param onlineJob id to edit
*/
void slotEditOnlineJob(const QString);
/**
* @brief Start dialog to edit onlineJob if possible
*/
void slotEditOnlineJob(const onlineJob);
/**
* @brief Start dialog to edit onlineJob if possible
*/
void slotEditOnlineJob(const onlineJobTyped<creditTransfer>);
/**
* @brief Saves an online banking job
*/
void slotOnlineJobSave(onlineJob job);
/**
* @brief Queue an online banking job
*/
void slotOnlineJobSend(onlineJob job);
/**
* @brief Send a list of onlineJobs
*/
void slotOnlineJobSend(QList<onlineJob> jobs);
/**
* dummy method needed just for initialization
*/
void slotRemoveJob();
void slotEditJob();
/**
* @brief Show the log currently selected online job
*/
void slotOnlineJobLog();
void slotOnlineJobLog(const QStringList& onlineJobIds);
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<QString> 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);
/**
* This method opens the category editor with the data found in @a account. The
* parent account is preset to @a parent but can be modified. If the user
* acknowledges, the category is created.
*/
void createCategory(MyMoneyAccount& account, const MyMoneyAccount& parent);
/**
* This method returns the account for a given @a key - @a value pair.
* If the account is not found in the list of accounts, MyMoneyAccount()
* is returned. The @a key - @a value pair can be in the account's kvp
* container or the account's online settings kvp container.
*/
const MyMoneyAccount& account(const QString& key, const QString& value) const;
/**
* This method set the online parameters stored in @a kvps with the
* account referenced by @a acc.
*/
void setAccountOnlineParameters(const MyMoneyAccount& acc, const MyMoneyKeyValueContainer& kvps);
QUrl selectFile(const QString& title, const QString& path, const QString& mask, QFileDialog::FileMode, QWidget *widget);
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();
/**
* load all available plugins. Make sure you have called createInterfaces()
* before you call this one.
*/
void loadPlugins();
/**
* unload all available plugins. Make sure you have called loadPlugins()
* before you call this one.
*/
void unloadPlugins();
/**
* @brief Checks if the given plugin is loaded on start up
*
* This filters plugins which are loaded on demand only and deactivated plugins.
* The configGroup must point to the correct group already.
*/
bool isPluginEnabled(const KPluginMetaData& metaData, const KConfigGroup& configGroup);
/**
* read general options again and initialize all variables like the recent file list
*/
void readOptions();
/** initializes the KActions of the application */
void initActions();
/** initializes the dynamic menus (account selectors) */
void initDynamicMenus();
/**
* sets up the statusbar for the main window by initialzing a statuslabel.
*/
void initStatusBar();
/**
* @brief Establish connections between actions and views
*
* Must be called after creation of actions and views.
*/
void connectActionsAndViews();
/** 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 checks, if an account can be closed or not. An account
* can be closed if:
*
* - the balance is zero and
* - all children are already closed and
* - there is no unfinished schedule referencing the account
*
* @param acc reference to MyMoneyAccount object in question
* @retval true account can be closed
* @retval false account cannot be closed
*/
KMyMoneyUtils::CanCloseAccountCodeE canCloseAccount(const MyMoneyAccount& acc) const;
/**
* This method checks if an account can be closed and enables/disables
* the close account action
* If disabled, it sets a tooltip explaning why it cannot be closed
* @brief enableCloseAccountAction
* @param acc reference to MyMoneyAccount object in question
*/
void enableCloseAccountAction(const MyMoneyAccount& acc);
/**
* Check if a list contains a payee with a given id
*
* @param list const reference to value list
* @param id const reference to id
*
* @retval true object has been found
* @retval false object is not in list
*/
bool payeeInList(const QList<MyMoneyPayee>& list, const QString& id) const;
/**
* Check if a list contains a tag with a given id
*
* @param list const reference to value list
* @param id const reference to id
*
* @retval true object has been found
* @retval false object is not in list
*/
bool tagInList(const QList<MyMoneyTag>& list, const QString& id) const;
/**
* Mark the selected transactions as provided by @a flag. If
* flag is @a MyMoneySplit::Unknown, the future state depends
* on the current stat of the split's flag accoring to the
* following table:
*
* - NotReconciled --> Cleared
* - Cleared --> Reconciled
* - Reconciled --> NotReconciled
*/
void markTransaction(MyMoneySplit::reconcileFlagE flag);
/**
* This method allows to skip the next scheduled transaction of
* the given schedule @a s.
*
*/
void skipSchedule(MyMoneySchedule& s);
/**
* This method allows to enter the next scheduled transaction of
* the given schedule @a s. In case @a extendedKeys is @a true,
* the given schedule can also be skipped or ignored.
* If @a autoEnter is @a true and the schedule does not contain
* an estimated value, the schedule is entered as is without further
* interaction with the user. In all other cases, the user will
* be presented a dialog and allowed to adjust the values for this
* instance of the schedule.
*
* The transaction will be created and entered into the ledger
* and the schedule updated.
*/
KMyMoneyUtils::EnterScheduleResultCodeE enterSchedule(MyMoneySchedule& s, bool autoEnter = false, bool extendedKeys = false);
/**
* Creates a new institution entry in the MyMoneyFile engine
*
* @param institution MyMoneyInstitution object containing the data of
* the institution to be created.
*/
void createInstitution(MyMoneyInstitution& institution);
/**
* This method unmatches the currently selected transactions
*/
void transactionUnmatch();
/**
* This method matches the currently selected transactions
*/
void transactionMatch();
/**
* This method preloads the holidays for the duration of the default forecast period
*/
void preloadHolidays();
public 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();
/**
* Imports a KMM statement into the engine, triggering the appropriate
* UI to handle account matching, payee creation, and someday
* payee and transaction matching.
*/
bool slotStatementImport(const MyMoneyStatement& s, bool silent = false);
/**
* Essentially similar to the above slot, except this will load the file
* from disk first, given the URL.
*/
bool slotStatementImport(const QString& url);
/**
* This slot starts the reconciliation of the currently selected account
*/
void slotAccountReconcileStart();
/**
* This slot finishes a previously started reconciliation
*/
void slotAccountReconcileFinish();
/**
* This slot postpones a previously started reconciliations
*/
void slotAccountReconcilePostpone();
/**
* This slot deletes the currently selected account if possible
*/
void slotAccountDelete();
/**
* This slot opens the account editor to edit the currently
* selected account if possible
*/
void slotAccountEdit();
/**
* This slot opens the selected account in the ledger view
*/
void slotAccountOpenEmpty();
void slotAccountOpen(const MyMoneyObject&);
/**
* Preloads the input dialog with the data of the current
* selected institution and brings up the input dialog
* and saves the information entered.
*/
void slotInstitutionEditEmpty();
void slotInstitutionEdit(const MyMoneyObject &obj);
/**
* Deletes the current selected institution.
*/
void slotInstitutionDelete();
/**
* This slot closes the currently selected account if possible
*/
void slotAccountClose();
/**
* This slot re-openes the currently selected account if possible
*/
void slotAccountReopen();
/**
* 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);
/**
* This slot creates a transaction report for the selected account
* and opens it in the reports view.
*/
void slotAccountTransactionReport();
/**
* This slot opens the account options menu at the current cursor
* position.
*/
void slotShowAccountContextMenu(const MyMoneyObject&);
/**
* This slot opens the schedule options menu at the current cursor
* position.
*/
void slotShowScheduleContextMenu();
/**
* This slot opens the institution options menu at the current cursor
* position.
*/
void slotShowInstitutionContextMenu(const MyMoneyObject&);
/**
* This slot opens the investment options menu at the current cursor
* position.
*/
void slotShowInvestmentContextMenu();
/**
* This slot opens the payee options menu at the current cursor
* position.
*/
void slotShowPayeeContextMenu();
/**
* This slot opens the tag options menu at the current cursor
* position.
*/
void slotShowTagContextMenu();
/**
* This slot opens the budget options menu at the current cursor
* position.
*/
void slotShowBudgetContextMenu();
/**
* This slot opens the transaction options menu at the current cursor
* position.
*/
void slotShowTransactionContextMenu();
/**
* This slot opens the currency options menu at the current cursor
* position.
*/
void slotShowCurrencyContextMenu();
/**
* This slot opens the price options menu at the current cursor
* position.
*/
void slotShowPriceContextMenu();
/**
* Open onlineJob options menu at current cursor position.
*/
void slotShowOnlineJobContextMenu();
/**
* This slot collects information for a new scheduled transaction
* and saves it in the engine. @sa slotScheduleNew(const MyMoneyTransaction&)
*/
void slotScheduleNew();
/**
* This slot allows to edit information the currently selected schedule
*/
void slotScheduleEdit();
/**
* This slot allows to delete the currently selected schedule
*/
void slotScheduleDelete();
/**
* This slot allows to enter the next scheduled transaction of
* the currently selected schedule
*/
void slotScheduleEnter();
/**
* This slot allows to skip the next scheduled transaction of
* the currently selected schedule
*/
void slotScheduleSkip();
/**
* This slot fires up the KCalc application
*/
void slotToolsStartKCalc();
void slotResetSelections();
void slotSelectAccount(const MyMoneyObject& account);
void slotSelectInstitution(const MyMoneyObject& institution);
void slotSelectInvestment(const MyMoneyObject& account);
void slotSelectSchedule();
void slotSelectSchedule(const MyMoneySchedule& schedule);
void slotSelectPayees(const QList<MyMoneyPayee>& list);
void slotSelectTags(const QList<MyMoneyTag>& list);
void slotSelectBudget(const QList<MyMoneyBudget>& list);
void slotSelectTransactions(const KMyMoneyRegister::SelectedTransactions& list);
void slotSelectCurrency();
void slotSelectCurrency(const MyMoneySecurity& currency);
void slotSelectPrice();
void slotSelectPrice(const MyMoneyPrice& price);
void slotTransactionMatch();
/**
* Brings up the new account wizard and saves the information.
*/
void slotAccountNew();
void slotAccountNew(MyMoneyAccount&);
/**
* Brings up the new category editor and saves the information.
*/
void slotCategoryNew();
/**
* 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 = MyMoneyAccount());
/**
* This method updates all KAction items to the current state.
*/
void slotUpdateActions();
/**
* Brings up the input dialog and saves the information.
*/
void slotInstitutionNew();
/**
* Brings up the input dialog and saves the information. If
* the institution has been created, the @a id member is filled,
* otherwise it is empty.
*
* @param institution reference to data to be used to create the
* institution. id member will be updated.
*/
void slotInstitutionNew(MyMoneyInstitution& institution);
/**
* Loads a plugin
*/
void slotPluginLoad(const KPluginMetaData& metaData);
/**
* Unloads a plugin
*/
void slotPluginUnload(const KPluginMetaData& metaData);
void webConnect(const QString& sourceUrl, const QByteArray &asn_id);
void webConnect(const QUrl url) { webConnect(url.path(), QByteArray()); }
private:
/**
* Create the transaction move menu and setup necessary connections.
*/
void createTransactionMoveMenu();
/**
* 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();
/**
* Delete a possibly existing transaction editor but make sure to remove
* any reference to it so that we avoid using a half-dead object
*/
void deleteTransactionEditor();
/**
* delete all selected transactions w/o further questions
*/
void doDeleteTransactions();
/**
* 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();
/**
* Implement common task when deleting or merging payees
*/
bool payeeReassign(int type);
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 payee/list of payees has been selected by
* the GUI. If no payee is selected or the selection is removed,
* @p payees is identical to an empty QList. This signal is used
* by plugins to get information about changes.
*/
void payeesSelected(const QList<MyMoneyPayee>& payees);
/**
* This signal is emitted when a tag/list of tags has been selected by
* the GUI. If no tag is selected or the selection is removed,
* @p tags is identical to an empty QList. This signal is used
* by plugins to get information about changes.
*/
void tagsSelected(const QList<MyMoneyTag>& tags);
/**
* 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 list of budgets has been selected by
* the GUI. If no budget is selected or the selection is removed,
* @a budget is identical to an empty QList. This signal is used
* by plugins to get information about changes.
*/
void budgetSelected(const QList<MyMoneyBudget>& budget);
void budgetRename();
/**
* 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);
void investmentSelected(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);
/**
* This signal is emitted when a new currency has been selected by
* the GUI. If no currency is selected or the selection is removed,
* @a currency is identical to MyMoneySecurity(). This signal is used
* by plugins to get information about changes.
*/
void currencySelected(const MyMoneySecurity& currency);
/**
* This signal is emitted when a new price has been selected by
* the GUI. If no price is selected or the selection is removed,
* @a price is identical to MyMoneyPrice().
*/
void priceSelected(const MyMoneyPrice& price);
void payeeRename();
void payeeCreated(const QString& id);
void tagRename();
void tagCreated(const QString& id);
void currencyRename();
void currencyCreated(const QString& id);
void priceEdit();
void priceNew();
void priceDelete();
void priceOnlineUpdate();
void startMatchTransaction(const MyMoneyTransaction& t);
void cancelMatchTransaction();
/**
* 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<QPair<MyMoneyTransaction, MyMoneySplit> >& transactionList);
public:
bool isActionToggled(const Action _a);
static const QHash<Action, QString> 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;
typedef void(KMyMoneyApp::*KMyMoneyAppFunc)();
class KMStatus
{
public:
KMStatus(const QString &text);
~KMStatus();
private:
QString m_prevText;
};
#define KMSTATUS(msg) KMStatus _thisStatus(msg)
#endif // KMYMONEY_H
diff --git a/kmymoney/kmymoneyutils.cpp b/kmymoney/kmymoneyutils.cpp
index 9abf7fd22..0ea51ff7a 100644
--- a/kmymoney/kmymoneyutils.cpp
+++ b/kmymoney/kmymoneyutils.cpp
@@ -1,686 +1,687 @@
/***************************************************************************
kmymoneyutils.cpp - description
-------------------
begin : Wed Feb 5 2003
copyright : (C) 2000-2003 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@users.sourceforge.net>
(C) 2017 by Łukasz Wojniłowicz <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 "kmymoneyutils.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QApplication>
#include <QList>
#include <QPixmap>
#include <QWizard>
#include <QAbstractButton>
#include <QPixmapCache>
#include <QIcon>
#include <QPainter>
#include <QBitArray>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
// ----------------------------------------------------------------------------
// KDE Headers
#include <KColorScheme>
#include <KLocalizedString>
#include <KXmlGuiWindow>
#include <KMessageBox>
#include <KStandardGuiItem>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneyprice.h"
#include "mymoneyforecast.h"
#include "kmymoneyglobalsettings.h"
#include "icons.h"
#include "storageenums.h"
using namespace Icons;
KMyMoneyUtils::KMyMoneyUtils()
{
}
KMyMoneyUtils::~KMyMoneyUtils()
{
}
-const QString KMyMoneyUtils::accountTypeToString(const MyMoneyAccount::accountTypeE accountType)
+const QString KMyMoneyUtils::accountTypeToString(const eMyMoney::Account accountType)
{
return MyMoneyAccount::accountTypeToString(accountType);
}
-MyMoneyAccount::accountTypeE KMyMoneyUtils::stringToAccountType(const QString& type)
+eMyMoney::Account KMyMoneyUtils::stringToAccountType(const QString& type)
{
- MyMoneyAccount::accountTypeE rc = MyMoneyAccount::UnknownAccountType;
+ eMyMoney::Account rc = eMyMoney::Account::Unknown;
QString tmp = type.toLower();
if (tmp == i18n("Checking").toLower())
- rc = MyMoneyAccount::Checkings;
+ rc = eMyMoney::Account::Checkings;
else if (tmp == i18n("Savings").toLower())
- rc = MyMoneyAccount::Savings;
+ rc = eMyMoney::Account::Savings;
else if (tmp == i18n("Credit Card").toLower())
- rc = MyMoneyAccount::CreditCard;
+ rc = eMyMoney::Account::CreditCard;
else if (tmp == i18n("Cash").toLower())
- rc = MyMoneyAccount::Cash;
+ rc = eMyMoney::Account::Cash;
else if (tmp == i18n("Loan").toLower())
- rc = MyMoneyAccount::Loan;
+ rc = eMyMoney::Account::Loan;
else if (tmp == i18n("Certificate of Deposit").toLower())
- rc = MyMoneyAccount::CertificateDep;
+ rc = eMyMoney::Account::CertificateDep;
else if (tmp == i18n("Investment").toLower())
- rc = MyMoneyAccount::Investment;
+ rc = eMyMoney::Account::Investment;
else if (tmp == i18n("Money Market").toLower())
- rc = MyMoneyAccount::MoneyMarket;
+ rc = eMyMoney::Account::MoneyMarket;
else if (tmp == i18n("Asset").toLower())
- rc = MyMoneyAccount::Asset;
+ rc = eMyMoney::Account::Asset;
else if (tmp == i18n("Liability").toLower())
- rc = MyMoneyAccount::Liability;
+ rc = eMyMoney::Account::Liability;
else if (tmp == i18n("Currency").toLower())
- rc = MyMoneyAccount::Currency;
+ rc = eMyMoney::Account::Currency;
else if (tmp == i18n("Income").toLower())
- rc = MyMoneyAccount::Income;
+ rc = eMyMoney::Account::Income;
else if (tmp == i18n("Expense").toLower())
- rc = MyMoneyAccount::Expense;
+ rc = eMyMoney::Account::Expense;
else if (tmp == i18n("Investment Loan").toLower())
- rc = MyMoneyAccount::AssetLoan;
+ rc = eMyMoney::Account::AssetLoan;
else if (tmp == i18n("Stock").toLower())
- rc = MyMoneyAccount::Stock;
+ rc = eMyMoney::Account::Stock;
else if (tmp == i18n("Equity").toLower())
- rc = MyMoneyAccount::Equity;
+ rc = eMyMoney::Account::Equity;
return rc;
}
-const QString KMyMoneyUtils::occurrenceToString(const MyMoneySchedule::occurrenceE occurrence)
+const QString KMyMoneyUtils::occurrenceToString(const eMyMoney::Schedule::Occurrence occurrence)
{
return i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(occurrence).toLatin1());
}
-const QString KMyMoneyUtils::paymentMethodToString(MyMoneySchedule::paymentTypeE paymentType)
+const QString KMyMoneyUtils::paymentMethodToString(eMyMoney::Schedule::PaymentType paymentType)
{
return i18nc("Scheduled Transaction payment type", MyMoneySchedule::paymentMethodToString(paymentType).toLatin1());
}
-const QString KMyMoneyUtils::weekendOptionToString(MyMoneySchedule::weekendOptionE weekendOption)
+const QString KMyMoneyUtils::weekendOptionToString(eMyMoney::Schedule::WeekendOption weekendOption)
{
return i18n(MyMoneySchedule::weekendOptionToString(weekendOption).toLatin1());
}
-const QString KMyMoneyUtils::scheduleTypeToString(MyMoneySchedule::typeE type)
+const QString KMyMoneyUtils::scheduleTypeToString(eMyMoney::Schedule::Type type)
{
return i18nc("Scheduled transaction type", MyMoneySchedule::scheduleTypeToString(type).toLatin1());
}
KGuiItem KMyMoneyUtils::scheduleNewGuiItem()
{
KGuiItem splitGuiItem(i18n("&New Schedule..."),
QIcon::fromTheme(g_Icons[Icon::DocumentNew]),
i18n("Create a new schedule."),
i18n("Use this to create a new schedule."));
return splitGuiItem;
}
KGuiItem KMyMoneyUtils::accountsFilterGuiItem()
{
KGuiItem splitGuiItem(i18n("&Filter"),
QIcon::fromTheme(g_Icons[Icon::ViewFilter]),
i18n("Filter out accounts"),
i18n("Use this to filter out accounts"));
return splitGuiItem;
}
const char* homePageItems[] = {
I18N_NOOP("Payments"),
I18N_NOOP("Preferred accounts"),
I18N_NOOP("Payment accounts"),
I18N_NOOP("Favorite reports"),
I18N_NOOP("Forecast (schedule)"),
I18N_NOOP("Net worth forecast"),
I18N_NOOP("Forecast (history)"),
I18N_NOOP("Assets and Liabilities"),
I18N_NOOP("Budget"),
I18N_NOOP("CashFlow"),
// insert new items above this comment
0
};
const QString KMyMoneyUtils::homePageItemToString(const int idx)
{
QString rc;
if (abs(idx) > 0 && abs(idx) < static_cast<int>(sizeof(homePageItems) / sizeof(homePageItems[0]))) {
rc = i18n(homePageItems[abs(idx-1)]);
}
return rc;
}
int KMyMoneyUtils::stringToHomePageItem(const QString& txt)
{
int idx = 0;
for (idx = 0; homePageItems[idx] != 0; ++idx) {
if (txt == i18n(homePageItems[idx]))
return idx + 1;
}
return 0;
}
bool KMyMoneyUtils::appendCorrectFileExt(QString& str, const QString& strExtToUse)
{
bool rc = false;
if (!str.isEmpty()) {
//find last . delminator
int nLoc = str.lastIndexOf('.');
if (nLoc != -1) {
QString strExt, strTemp;
strTemp = str.left(nLoc + 1);
strExt = str.right(str.length() - (nLoc + 1));
if (strExt.indexOf(strExtToUse, 0, Qt::CaseInsensitive) == -1) {
// if the extension given contains a period, we remove our's
if (strExtToUse.indexOf('.') != -1)
strTemp = strTemp.left(strTemp.length() - 1);
//append extension to make complete file name
strTemp.append(strExtToUse);
str = strTemp;
rc = true;
}
} else {
str.append(".");
str.append(strExtToUse);
rc = true;
}
}
return rc;
}
void KMyMoneyUtils::checkConstants()
{
// TODO: port to kf5
#if 0
Q_ASSERT(static_cast<int>(KLocale::ParensAround) == static_cast<int>(MyMoneyMoney::ParensAround));
Q_ASSERT(static_cast<int>(KLocale::BeforeQuantityMoney) == static_cast<int>(MyMoneyMoney::BeforeQuantityMoney));
Q_ASSERT(static_cast<int>(KLocale::AfterQuantityMoney) == static_cast<int>(MyMoneyMoney::AfterQuantityMoney));
Q_ASSERT(static_cast<int>(KLocale::BeforeMoney) == static_cast<int>(MyMoneyMoney::BeforeMoney));
Q_ASSERT(static_cast<int>(KLocale::AfterMoney) == static_cast<int>(MyMoneyMoney::AfterMoney));
#endif
}
QString KMyMoneyUtils::variableCSS()
{
QColor tcolor = KColorScheme(QPalette::Active).foreground(KColorScheme::NormalText).color();
QColor link = KColorScheme(QPalette::Active).foreground(KColorScheme::LinkText).color();
QString css;
css += "<style type=\"text/css\">\n<!--\n";
css += QString(".row-even, .item0 { background-color: %1; color: %2 }\n")
.arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListBackground1).name()).arg(tcolor.name());
css += QString(".row-odd, .item1 { background-color: %1; color: %2 }\n")
.arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListBackground2).name()).arg(tcolor.name());
css += QString("a { color: %1 }\n").arg(link.name());
css += "-->\n</style>\n";
return css;
}
QString KMyMoneyUtils::findResource(QStandardPaths::StandardLocation type, const QString& filename)
{
QLocale locale;
QString country;
QString localeName = locale.bcp47Name();
QString language = localeName;
// extract language and country from the bcp47name
QRegularExpression regExp(QLatin1String("(\\w+)_(\\w+)"));
QRegularExpressionMatch match = regExp.match(localeName);
if (match.hasMatch()) {
language = match.captured(1);
country = match.captured(2);
}
QString rc;
// check that the placeholder is present and set things up
if (filename.indexOf("%1") != -1) {
/// @fixme somehow I have the impression, that language and country
/// mappings to the filename are not correct. This certainly must
/// be overhauled at some point in time (ipwizard, 2017-10-22)
QString mask = filename.arg("_%1.%2");
rc = QStandardPaths::locate(type, mask.arg(country).arg(language));
// search the given resource
if (rc.isEmpty()) {
mask = filename.arg("_%1");
rc = QStandardPaths::locate(type, mask.arg(language));
}
if (rc.isEmpty()) {
// qDebug(QString("html/home_%1.html not found").arg(country).toLatin1());
rc = QStandardPaths::locate(type, mask.arg(country));
}
if (rc.isEmpty()) {
rc = QStandardPaths::locate(type, filename.arg(""));
}
} else {
rc = QStandardPaths::locate(type, filename);
}
if (rc.isEmpty()) {
qWarning("No resource found for (%s,%s)", qPrintable(QStandardPaths::displayName(type)), qPrintable(filename));
}
return rc;
}
const MyMoneySplit KMyMoneyUtils::stockSplit(const MyMoneyTransaction& t)
{
QList<MyMoneySplit>::ConstIterator it_s;
MyMoneySplit investmentAccountSplit;
for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
if (!(*it_s).accountId().isEmpty()) {
MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
if (acc.isInvest()) {
return *it_s;
}
// if we have a reference to an investment account, we remember it here
- if (acc.accountType() == MyMoneyAccount::Investment)
+ if (acc.accountType() == eMyMoney::Account::Investment)
investmentAccountSplit = *it_s;
}
}
// if we haven't found a stock split, we see if we've seen
// an investment account on the way. If so, we return it.
if (!investmentAccountSplit.id().isEmpty())
return investmentAccountSplit;
// if none was found, we return an empty split.
return MyMoneySplit();
}
KMyMoneyUtils::transactionTypeE KMyMoneyUtils::transactionType(const MyMoneyTransaction& t)
{
if (!stockSplit(t).id().isEmpty())
return InvestmentTransaction;
if (t.splitCount() < 2) {
return Unknown;
} else if (t.splitCount() > 2) {
// FIXME check for loan transaction here
return SplitTransaction;
}
QString ida, idb;
if (t.splits().size() > 0)
ida = t.splits()[0].accountId();
if (t.splits().size() > 1)
idb = t.splits()[1].accountId();
if (ida.isEmpty() || idb.isEmpty())
return Unknown;
MyMoneyAccount a, b;
a = MyMoneyFile::instance()->account(ida);
b = MyMoneyFile::instance()->account(idb);
- if ((a.accountGroup() == MyMoneyAccount::Asset
- || a.accountGroup() == MyMoneyAccount::Liability)
- && (b.accountGroup() == MyMoneyAccount::Asset
- || b.accountGroup() == MyMoneyAccount::Liability))
+ if ((a.accountGroup() == eMyMoney::Account::Asset
+ || a.accountGroup() == eMyMoney::Account::Liability)
+ && (b.accountGroup() == eMyMoney::Account::Asset
+ || b.accountGroup() == eMyMoney::Account::Liability))
return Transfer;
return Normal;
}
void KMyMoneyUtils::calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap<QString, MyMoneyMoney>& balances)
{
try {
MyMoneyForecast::calculateAutoLoan(schedule, transaction, balances);
} catch (const MyMoneyException &e) {
KMessageBox::detailedError(0, i18n("Unable to load schedule details"), e.what());
}
}
QString KMyMoneyUtils::nextCheckNumber(const MyMoneyAccount& acc)
{
QString number;
// +-#1--+ +#2++-#3-++-#4--+
QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?"));
if (exp.indexIn(acc.value("lastNumberUsed")) != -1) {
setLastNumberUsed(acc.value("lastNumberUsed"));
QString arg1 = exp.cap(1);
QString arg2 = exp.cap(2);
QString arg3 = QString::number(exp.cap(3).toULong() + 1);
QString arg4 = exp.cap(4);
number = QString("%1%2%3%4").arg(arg1).arg(arg2).arg(arg3).arg(arg4);
// if new number is longer than previous one and we identified
// preceding 0s, then remove one of the preceding zeros
if (arg2.length() > 0 && (number.length() != acc.value("lastNumberUsed").length())) {
arg2 = arg2.mid(1);
number = QString("%1%2%3%4").arg(arg1).arg(arg2).arg(arg3).arg(arg4);
}
} else {
number = '1';
}
return number;
}
void KMyMoneyUtils::updateLastNumberUsed(const MyMoneyAccount& acc, const QString& number)
{
MyMoneyAccount accnt = acc;
QString num = number;
// now check if this number has been used already
MyMoneyFile* file = MyMoneyFile::instance();
if (file->checkNoUsed(accnt.id(), num)) {
// if a number has been entered which is immediately prior to
// an existing number, the next new number produced would clash
// so need to look ahead for free next number
bool free = false;
for (int i = 0; i < 10; i++) {
// find next unused number - 10 tries (arbitrary)
if (file->checkNoUsed(accnt.id(), num)) {
// increment and try again
num = getAdjacentNumber(num);
} else {
// found a free number
free = true;
break;
}
}
if (!free) {
qDebug() << "No free number found - set to '1'";
num = '1';
}
setLastNumberUsed(getAdjacentNumber(num, - 1));
}
}
void KMyMoneyUtils::setLastNumberUsed(const QString& num)
{
m_lastNumberUsed = num;
}
QString KMyMoneyUtils::lastNumberUsed()
{
return m_lastNumberUsed;
}
QString KMyMoneyUtils::getAdjacentNumber(const QString& number, int offset)
{
QString num = number;
// +-#1--+ +#2++-#3-++-#4--+
QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?"));
if (exp.indexIn(num) != -1) {
QString arg1 = exp.cap(1);
QString arg2 = exp.cap(2);
QString arg3 = QString::number(exp.cap(3).toULong() + offset);
QString arg4 = exp.cap(4);
num = QString("%1%2%3%4").arg(arg1).arg(arg2).arg(arg3).arg(arg4);
} else {
num = '1';
} // next free number
return num;
}
quint64 KMyMoneyUtils::numericPart(const QString & num)
{
quint64 num64 = 0;
QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?"));
if (exp.indexIn(num) != -1) {
QString arg1 = exp.cap(1);
QString arg2 = exp.cap(2);
QString arg3 = QString::number(exp.cap(3).toULongLong());
QString arg4 = exp.cap(4);
num64 = QString("%2%3").arg(arg2).arg(arg3).toULongLong();
}
return num64;
}
QString KMyMoneyUtils::reconcileStateToString(MyMoneySplit::reconcileFlagE flag, bool text)
{
QString txt;
if (text) {
switch (flag) {
case MyMoneySplit::NotReconciled:
txt = i18nc("Reconciliation state 'Not reconciled'", "Not reconciled");
break;
case MyMoneySplit::Cleared:
txt = i18nc("Reconciliation state 'Cleared'", "Cleared");
break;
case MyMoneySplit::Reconciled:
txt = i18nc("Reconciliation state 'Reconciled'", "Reconciled");
break;
case MyMoneySplit::Frozen:
txt = i18nc("Reconciliation state 'Frozen'", "Frozen");
break;
default:
txt = i18nc("Unknown reconciliation state", "Unknown");
break;
}
} else {
switch (flag) {
case MyMoneySplit::NotReconciled:
break;
case MyMoneySplit::Cleared:
txt = i18nc("Reconciliation flag C", "C");
break;
case MyMoneySplit::Reconciled:
txt = i18nc("Reconciliation flag R", "R");
break;
case MyMoneySplit::Frozen:
txt = i18nc("Reconciliation flag F", "F");
break;
default:
txt = i18nc("Flag for unknown reconciliation state", "?");
break;
}
}
return txt;
}
MyMoneyTransaction KMyMoneyUtils::scheduledTransaction(const MyMoneySchedule& schedule)
{
MyMoneyTransaction t = schedule.transaction();
try {
- if (schedule.type() == MyMoneySchedule::TYPE_LOANPAYMENT) {
+ if (schedule.type() == eMyMoney::Schedule::Type::LoanPayment) {
calculateAutoLoan(schedule, t, QMap<QString, MyMoneyMoney>());
}
} catch (const MyMoneyException &e) {
qDebug("Unable to load schedule details for '%s' during transaction match: %s", qPrintable(schedule.name()), qPrintable(e.what()));
}
t.clearId();
t.setEntryDate(QDate());
return t;
}
KXmlGuiWindow* KMyMoneyUtils::mainWindow()
{
foreach (QWidget *widget, QApplication::topLevelWidgets()) {
KXmlGuiWindow* result = dynamic_cast<KXmlGuiWindow*>(widget);
if (result)
return result;
}
return 0;
}
void KMyMoneyUtils::updateWizardButtons(QWizard* wizard)
{
// setup text on buttons
wizard->setButtonText(QWizard::NextButton, i18nc("Go to next page of the wizard", "&Next"));
wizard->setButtonText(QWizard::BackButton, KStandardGuiItem::back().text());
// setup icons
wizard->button(QWizard::FinishButton)->setIcon(KStandardGuiItem::ok().icon());
wizard->button(QWizard::CancelButton)->setIcon(KStandardGuiItem::cancel().icon());
wizard->button(QWizard::NextButton)->setIcon(KStandardGuiItem::forward(KStandardGuiItem::UseRTL).icon());
wizard->button(QWizard::BackButton)->setIcon(KStandardGuiItem::back(KStandardGuiItem::UseRTL).icon());
}
QPixmap KMyMoneyUtils::overlayIcon(const QString &iconName, const QString &overlayName, const Qt::Corner corner, const int size)
{
QPixmap pxIcon;
QString kyIcon = iconName + overlayName;
// If found in the cache, return quickly
if (QPixmapCache::find(kyIcon, pxIcon))
return pxIcon;
// try to retrieve the main icon from cache
if (!QPixmapCache::find(iconName, pxIcon)) {
pxIcon = QIcon::fromTheme(iconName).pixmap(size);
QPixmapCache::insert(iconName, pxIcon);
}
if (overlayName.isEmpty()) // call from MyMoneyAccount::accountPixmap can have no overlay icon, so handle that appropriately
return pxIcon;
QPainter pixmapPainter(&pxIcon);
QPixmap pxOverlay = QIcon::fromTheme(overlayName).pixmap(size);
int x, y;
switch (corner) {
case Qt::TopLeftCorner:
x = 0;
y = 0;
break;
case Qt::TopRightCorner:
x = pxIcon.width() / 2;
y = 0;
break;
case Qt::BottomLeftCorner:
x = 0;
y = pxIcon.height() / 2;
break;
case Qt::BottomRightCorner:
default:
x = pxIcon.width() / 2;
y = pxIcon.height() / 2;
break;
}
pixmapPainter.drawPixmap(x, y, pxIcon.width() / 2, pxIcon.height() / 2, pxOverlay);
//save for later use
QPixmapCache::insert(kyIcon, pxIcon);
return pxIcon;
}
void KMyMoneyUtils::dissectTransaction(const MyMoneyTransaction& transaction, const MyMoneySplit& split, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency, MyMoneySplit::investTransactionTypeE& transactionType)
{
// collect the splits. split references the stock account and should already
// be set up. assetAccountSplit references the corresponding asset account (maybe
// empty), feeSplits is the list of all expenses and interestSplits
// the list of all incomes
assetAccountSplit = MyMoneySplit(); // set to none to check later if it was assigned
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
MyMoneyAccount acc = file->account((*it_s).accountId());
if ((*it_s).id() == split.id()) {
security = file->security(acc.currencyId());
- } else if (acc.accountGroup() == MyMoneyAccount::Expense) {
+ } else if (acc.accountGroup() == eMyMoney::Account::Expense) {
feeSplits.append(*it_s);
// feeAmount += (*it_s).value();
- } else if (acc.accountGroup() == MyMoneyAccount::Income) {
+ } else if (acc.accountGroup() == eMyMoney::Account::Income) {
interestSplits.append(*it_s);
// interestAmount += (*it_s).value();
} else {
if (assetAccountSplit == MyMoneySplit()) // first asset Account should be our requested brokerage account
assetAccountSplit = *it_s;
else if ((*it_s).value().isNegative()) // the rest (if present) is handled as fee or interest
feeSplits.append(*it_s); // and shouldn't be allowed to override assetAccountSplit
else if ((*it_s).value().isPositive())
interestSplits.append(*it_s);
}
}
// determine transaction type
if (split.action() == MyMoneySplit::ActionAddShares) {
transactionType = (!split.shares().isNegative()) ? MyMoneySplit::AddShares : MyMoneySplit::RemoveShares;
} else if (split.action() == MyMoneySplit::ActionBuyShares) {
transactionType = (!split.value().isNegative()) ? MyMoneySplit::BuyShares : MyMoneySplit::SellShares;
} else if (split.action() == MyMoneySplit::ActionDividend) {
transactionType = MyMoneySplit::Dividend;
} else if (split.action() == MyMoneySplit::ActionReinvestDividend) {
transactionType = MyMoneySplit::ReinvestDividend;
} else if (split.action() == MyMoneySplit::ActionYield) {
transactionType = MyMoneySplit::Yield;
} else if (split.action() == MyMoneySplit::ActionSplitShares) {
transactionType = MyMoneySplit::SplitShares;
} else if (split.action() == MyMoneySplit::ActionInterestIncome) {
transactionType = MyMoneySplit::InterestIncome;
} else
transactionType = MyMoneySplit::BuyShares;
currency.setTradingSymbol("???");
try {
currency = file->security(transaction.commodity());
} catch (const MyMoneyException &) {
}
}
void KMyMoneyUtils::deleteSecurity(const MyMoneySecurity& security, QWidget* parent)
{
QString msg, msg2;
QString dontAsk, dontAsk2;
if (security.isCurrency()) {
msg = i18n("<p>Do you really want to remove the currency <b>%1</b> from the file?</p>", security.name());
msg2 = i18n("<p>All exchange rates for currency <b>%1</b> will be lost.</p><p>Do you still want to continue?</p>", security.name());
dontAsk = "DeleteCurrency";
dontAsk2 = "DeleteCurrencyRates";
} else {
msg = i18n("<p>Do you really want to remove the %1 <b>%2</b> from the file?</p>", MyMoneySecurity::securityTypeToString(security.securityType()), security.name());
msg2 = i18n("<p>All price quotes for %1 <b>%2</b> will be lost.</p><p>Do you still want to continue?</p>", MyMoneySecurity::securityTypeToString(security.securityType()), security.name());
dontAsk = "DeleteSecurity";
dontAsk2 = "DeleteSecurityPrices";
}
if (KMessageBox::questionYesNo(parent, msg, i18n("Delete security"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontAsk) == KMessageBox::Yes) {
MyMoneyFileTransaction ft;
MyMoneyFile* file = MyMoneyFile::instance();
QBitArray skip((int)eStorage::Reference::Count);
skip.fill(true);
skip.clearBit((int)eStorage::Reference::Price);
if (file->isReferenced(security, skip)) {
if (KMessageBox::questionYesNo(parent, msg2, i18n("Delete prices"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontAsk2) == KMessageBox::Yes) {
try {
QString secID = security.id();
foreach (auto priceEntry, file->priceList()) {
const MyMoneyPrice& price = priceEntry.first();
if (price.from() == secID || price.to() == secID)
file->removePrice(price);
}
ft.commit();
ft.restart();
} catch (const MyMoneyException &) {
qDebug("Cannot delete price");
return;
}
} else
return;
}
try {
if (security.isCurrency())
file->removeCurrency(security);
else
file->removeSecurity(security);
ft.commit();
} catch (const MyMoneyException &) {
}
}
}
diff --git a/kmymoney/kmymoneyutils.h b/kmymoney/kmymoneyutils.h
index ed8f2c872..d4952cc33 100644
--- a/kmymoney/kmymoneyutils.h
+++ b/kmymoney/kmymoneyutils.h
@@ -1,429 +1,429 @@
/***************************************************************************
kmymoneyutils.h - description
-------------------
begin : Wed Feb 5 2003
copyright : (C) 2000-2003 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 KMYMONEYUTILS_H
#define KMYMONEYUTILS_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QPixmap>
#include <QStandardPaths>
// ----------------------------------------------------------------------------
// KDE Headers
#include <KGuiItem>
class KXmlGuiWindow;
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyaccount.h"
#include "mymoneysecurity.h"
#include "mymoneyschedule.h"
#include "mymoneytransaction.h"
/**
* @author Thomas Baumgart
*/
static QString m_lastNumberUsed;
class QWizard;
class KMyMoneyUtils
{
public:
/**
* This enum is used to describe the bits of an account type filter mask.
* Each bit is used to define a specific account class. Multiple classes
* can be specified by OR'ing multiple entries. The special entry @p last
* marks the left most bit in the mask and is used by scanners of this
* bitmask to determine the end of processing.
*/
enum categoryTypeE {
none = 0x000, ///< no account class selected
liability = 0x001, ///< liability accounts selected
asset = 0x002, ///< asset accounts selected
expense = 0x004, ///< expense accounts selected
income = 0x008, ///< income accounts selected
equity = 0x010, ///< equity accounts selected
checking = 0x020, ///< checking accounts selected
savings = 0x040, ///< savings accounts selected
investment = 0x080, ///< investment accounts selected
creditCard = 0x100, ///< credit card accounts selected
last = 0x200 ///< the leftmost bit in the mask
};
enum transactionTypeE {
/**
* Unknown transaction type (e.g. used for a transaction with only
* a single split)
*/
Unknown,
/**
* A 'normal' transaction is one that consists out two splits: one
* referencing an income/expense account, the other referencing
* an asset/liability account.
*/
Normal,
/**
* A transfer denotes a transaction consisting of two splits.
* Both of the splits reference an asset/liability
* account.
*/
Transfer,
/**
* Whenever a transaction consists of more than 2 splits,
* it is treated as 'split transaction'.
*/
SplitTransaction,
/**
* This transaction denotes a specific transaction where
* a loan account is involved. Usually, a special dialog
* is used to modify this transaction.
*/
LoanPayment,
/**
* This transaction denotes a specific transaction where
* an investment is involved. Usually, a special dialog
* is used to modify this transaction.
*/
InvestmentTransaction
};
enum EnterScheduleResultCodeE {
Cancel = 0, // cancel the operation
Enter, // enter the schedule
Skip, // skip the schedule
Ignore // ignore the schedule
};
enum CanCloseAccountCodeE {
AccountCanClose = 0, // can close the account
AccountBalanceNonZero, // balance is non zero
AccountChildrenOpen, // account has open children account
AccountScheduleReference // account is referenced in a schedule
};
static const int maxHomePageItems = 5;
KMyMoneyUtils();
~KMyMoneyUtils();
/**
* This method is used to convert the internal representation of
* an account type into a human readable format
*
* @param accountType numerical representation of the account type.
- * For possible values, see MyMoneyAccount::accountTypeE
+ * For possible values, see eMyMoney::Account
* @return QString representing the human readable form translated according to the language cataglogue
*
* @sa MyMoneyAccount::accountTypeToString()
*/
- static const QString accountTypeToString(const MyMoneyAccount::accountTypeE accountType);
+ static const QString accountTypeToString(const eMyMoney::Account accountType);
/**
* This method is used to convert an account type from its
* string form to the internal used numeric value.
*
* @param type reference to a QString containing the string to convert
* @return accountTypeE containing the internal used numeric value. For possible
- * values see MyMoneyAccount::accountTypeE
+ * values see eMyMoney::Account
*/
- static MyMoneyAccount::accountTypeE stringToAccountType(const QString& type);
+ static eMyMoney::Account stringToAccountType(const QString& type);
/**
* This method is used to convert a rounding method from its
* string form to the internal used numeric value.
*
* @param txt reference to a QString containing the string to convert
* @return RoundingMethod containing the internal used numeric value. For possible
* values see AlkValue::RoundingMethod
*/
static AlkValue::RoundingMethod stringToRoundingMethod(const QString& txt);
/**
* This method is used to convert the internal representation of
* an rounding method into a human readable format
*
* @param roundingMethod enumerated representation of the rounding method.
* For possible values, see AlkValue::RoundingMethod
* @return QString representing the human readable form translated according to the language cataglogue
*
* @sa MyMoneySecurity::roundingMethodToString()
*/
static const QString roundingMethodToString(const AlkValue::RoundingMethod roundingMethod);
/**
* This method is used to convert the occurrence type from its
* internal representation into a human readable format.
*
* @param occurrence numerical representation of the MyMoneySchedule
* occurrence type
*
* @return QString representing the human readable format translated according to the language cataglogue
*
* @sa MyMoneySchedule::occurrenceToString()
*
* @deprecated Use i18n(MyMoneySchedule::occurrenceToString(occurrence)) instead
*/
- static const QString occurrenceToString(const MyMoneySchedule::occurrenceE occurrence);
+ static const QString occurrenceToString(const eMyMoney::Schedule::Occurrence occurrence);
/**
* This method is used to convert the payment type from its
* internal representation into a human readable format.
*
* @param paymentType numerical representation of the MyMoneySchedule
* payment type
*
* @return QString representing the human readable format translated according to the language cataglogue
*
* @sa MyMoneySchedule::paymentMethodToString()
*/
- static const QString paymentMethodToString(MyMoneySchedule::paymentTypeE paymentType);
+ static const QString paymentMethodToString(eMyMoney::Schedule::PaymentType paymentType);
/**
* This method is used to convert the schedule weekend option from its
* internal representation into a human readable format.
*
* @param weekendOption numerical representation of the MyMoneySchedule
* weekend option
*
* @return QString representing the human readable format translated according to the language cataglogue
*
* @sa MyMoneySchedule::weekendOptionToString()
*/
- static const QString weekendOptionToString(MyMoneySchedule::weekendOptionE weekendOption);
+ static const QString weekendOptionToString(eMyMoney::Schedule::WeekendOption weekendOption);
/**
* This method is used to convert the schedule type from its
* internal representation into a human readable format.
*
* @param type numerical representation of the MyMoneySchedule
* schedule type
*
* @return QString representing the human readable format translated according to the language cataglogue
*
* @sa MyMoneySchedule::scheduleTypeToString()
*/
- static const QString scheduleTypeToString(MyMoneySchedule::typeE type);
+ static const QString scheduleTypeToString(eMyMoney::Schedule::Type type);
/**
* This method is used to convert a numeric index of an item
* represented on the home page into its string form.
*
* @param idx numeric index of item
*
* @return QString with text of this item
*/
static const QString homePageItemToString(const int idx);
/**
* This method is used to convert the name of a home page item
* to its internal numerical representation
*
* @param txt QString reference of the items name
*
* @retval 0 @p txt is unknown
* @retval >0 numeric value for @p txt
*/
static int stringToHomePageItem(const QString& txt);
/**
* Retrieve a KDE KGuiItem for the new schedule button.
*
* @return The KGuiItem that can be used to display the icon and text
*/
static KGuiItem scheduleNewGuiItem();
/**
* Retrieve a KDE KGuiItem for the account filter button
*
* @return The KGuiItem that can be used to display the icon and text
*/
static KGuiItem accountsFilterGuiItem();
/**
* This method adds the file extension passed as argument @p extension
* to the end of the file name passed as argument @p name if it is not present.
* If @p name contains an extension it will be removed.
*
* @param name filename to be checked
* @param extension extension to be added (w/o the dot)
*
* @retval true if @p name was changed
* @retval false if @p name remained unchanged
*/
static bool appendCorrectFileExt(QString& name, const QString& extension);
/**
* Check that internal MyMoney engine constants use the same
* values as the KDE constants.
*/
static void checkConstants();
static QString variableCSS();
/**
* This method searches a KDE specific resource and applies country and
* language settings during the search. Therefore, the parameter @p filename must contain
* the characters '%1' which gets replaced with the language/country values.
*
* The search is performed in the following order (stopped immediately if a file was found):
* - @c \%1 is replaced with <tt>_\<country\>.\<language\></tt>
* - @c \%1 is replaced with <tt>_\<language\></tt>
* - @c \%1 is replaced with <tt>_\<country\></tt>
* - @c \%1 is replaced with the empty string
*
* @c \<country\> and @c \<language\> denote the respective KDE settings.
*
* Example: The KDE settings for country is Spain (es) and language is set
* to Galician (gl). The code for looking up a file looks like this:
*
* @code
*
* :
* QString fname = KMyMoneyUtils::findResource("appdata", "html/home%1.html")
* :
*
* @endcode
*
* The method calls KStandardDirs::findResource() with the following values for the
* parameter @p filename:
*
* - <tt>html/home_es.gl.html</tt>
* - <tt>html/home_gl.html</tt>
* - <tt>html/home_es.html</tt>
* - <tt>html/home.html</tt>
*
* @note See KStandardDirs::findResource() for details on the parameters
*/
static QString findResource(QStandardPaths::StandardLocation type, const QString& filename);
/**
* This method returns the split referencing a stock account if
* one exists in the transaction passed as @p t. If none is present
* in @p t, an empty MyMoneySplit() object will be returned.
*
* @param t transaction to be checked for a stock account
* @return MyMoneySplit object referencing a stock account or an
* empty MyMoneySplit object.
*/
static const MyMoneySplit stockSplit(const MyMoneyTransaction& t);
/**
* This method analyses the splits of a transaction and returns
* the type of transaction. Possible values are defined by the
* KMyMoneyUtils::transactionTypeE enum.
*
* @param t const reference to the transaction
*
* @return KMyMoneyUtils::transactionTypeE value of the action
*/
static transactionTypeE transactionType(const MyMoneyTransaction& t);
/**
* This method modifies a scheduled loan transaction such that all
* references to automatic calculated values are resolved to actual values.
*
* @param schedule const reference to the schedule the transaction is based on
* @param transaction reference to the transaction to be checked and modified
* @param balances QMap of (account-id,balance) pairs to be used as current balance
* for the calculation of interest. If map is empty, the engine
* will be interrogated for current balances.
*/
static void calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap<QString, MyMoneyMoney>& balances);
/**
* Return next check number for account @a acc.
*/
static QString nextCheckNumber(const MyMoneyAccount& acc);
static void updateLastNumberUsed(const MyMoneyAccount& acc, const QString& number);
static void setLastNumberUsed(const QString& num);
static QString lastNumberUsed();
/**
* Returns previous number if offset is -1 or
* the following number if offset is 1.
*/
static QString getAdjacentNumber(const QString& number, int offset = 1);
/**
* remove any non-numeric characters from check number
* to allow validity check
*/
static quint64 numericPart(const QString & num);
/**
* Returns the text representing the reconcile flag. If @a text is @p true
* then the full text will be returned otherwise a short form (usually one character).
*/
static QString reconcileStateToString(MyMoneySplit::reconcileFlagE flag, bool text = false);
/**
* Returns the transaction for @a schedule. In case of a loan payment the
* transaction will be modified by calculateAutoLoan().
* The ID of the transaction as well as the entryDate will be reset.
*
* @returns adjusted transaction
*/
static MyMoneyTransaction scheduledTransaction(const MyMoneySchedule& schedule);
/**
* This method replaces the deprecated QApplication::mainWidget() from Qt 3.x.
* It assumes that there is only one KXmlGuiWindow in the application, and
* returns it.
*
* @return the first KXmlGuiWindow found in QApplication::topLevelWidgets()
*/
static KXmlGuiWindow* mainWindow();
/**
* This method sets the button text and icons to the KDE standard ones
* for the QWizard passed as argument.
*/
static void updateWizardButtons(QWizard *);
/**
* This method overlays an icon over another one, to get a composite one
* eg. an icon to add accounts
*/
static QPixmap overlayIcon(const QString &iconName, const QString &overlayName, const Qt::Corner corner = Qt::BottomRightCorner, const int size = 64);
static void dissectTransaction(const MyMoneyTransaction& transaction, const MyMoneySplit& split, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency, MyMoneySplit::investTransactionTypeE& transactionType);
/**
* This method deletes security and associated price list but asks beforehand.
*/
static void deleteSecurity(const MyMoneySecurity &security, QWidget *parent = NULL);
};
#endif
diff --git a/kmymoney/models/accountsmodel.cpp b/kmymoney/models/accountsmodel.cpp
index 5afe8c86e..8bf369510 100644
--- a/kmymoney/models/accountsmodel.cpp
+++ b/kmymoney/models/accountsmodel.cpp
@@ -1,1224 +1,1229 @@
/***************************************************************************
* Copyright 2010 Cristian Onet onet.cristian@gmail.com *
* Copyright 2017 Łukasz Wojniłowicz 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) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 of the license. *
* *
* 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 <http://www.gnu.org/licenses/> *
***************************************************************************/
#include "accountsmodel.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KColorScheme>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyutils.h"
#include "mymoneyfile.h"
+#include "mymoneyinstitution.h"
+#include "mymoneyaccount.h"
+#include "mymoneysecurity.h"
+#include "mymoneyprice.h"
#include "kmymoneyglobalsettings.h"
#include "icons.h"
#include "modelenums.h"
using namespace Icons;
using namespace eAccountsModel;
+using namespace eMyMoney;
class AccountsModel::Private
{
public:
/**
* The pimpl.
*/
Private() :
m_file(MyMoneyFile::instance()) {
m_columns.append(Column::Account);
}
~Private() {
}
void loadPreferredAccount(const MyMoneyAccount &acc, QStandardItem *fromNode /*accounts' regular node*/, const int row, QStandardItem *toNode /*accounts' favourite node*/)
{
if (acc.value(QStringLiteral("PreferredAccount")) != QLatin1String("Yes"))
return;
auto favRow = toNode->rowCount();
auto favItem = itemFromAccountId(toNode, acc.id());
if (favItem)
favRow = favItem->row();
for (auto i = 0; i < fromNode->columnCount(); ++i) {
auto itemToClone = fromNode->child(row, i);
if (itemToClone)
toNode->setChild(favRow, i, itemToClone->clone());
}
}
/**
* Load all the sub-accounts recursively.
*
* @param model The model in which to load the data.
* @param accountsItem The item from the model of the parent account of the sub-accounts which are being loaded.
* @param favoriteAccountsItem The item of the favorites accounts groups so favorite accounts can be added here also.
* @param list The list of the account id's of the sub-accounts which are being loaded.
*
*/
void loadSubaccounts(QStandardItem *node, QStandardItem *favoriteAccountsItem, const QStringList& subaccounts)
{
if (subaccounts.isEmpty())
return;
foreach (const auto subaccStr, subaccounts) {
const auto subacc = m_file->account(subaccStr);
auto item = new QStandardItem(subacc.name()); // initialize first column of subaccount
node->appendRow(item); // add subaccount row to node
item->setEditable(false);
item->setData(node->data((int)Role::DisplayOrder), (int)Role::DisplayOrder); // inherit display order role from node
loadSubaccounts(item, favoriteAccountsItem, subacc.accountList()); // subaccount may have subaccounts as well
// set the account data after the children have been loaded
const auto row = item->row();
setAccountData(node, row, subacc, m_columns); // initialize rest of columns of subaccount
loadPreferredAccount(subacc, node, row, favoriteAccountsItem); // add to favourites node if preferred
}
}
/**
* Note: this functions should only be called after the child account data has been set.
*/
void setAccountData(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList<Column> &columns)
{
QStandardItem *cell;
auto getCell = [&, row](const auto column) {
cell = node->child(row, column); // try to get QStandardItem
if (!cell) { // it may be uninitialized
cell = new QStandardItem; // so create one
node->setChild(row, column, cell); // and add it under the node
}
};
auto colNum = m_columns.indexOf(Column::Account);
if (colNum == -1)
return;
getCell(colNum);
auto font = cell->data(Qt::FontRole).value<QFont>();
// display the names of closed accounts with strikeout font
if (account.isClosed() != font.strikeOut())
font.setStrikeOut(account.isClosed());
if (columns.contains(Column::Account)) {
// setting account column
cell->setData(account.name(), Qt::DisplayRole);
// cell->setData(QVariant::fromValue(account), (int)Role::Account); // is set in setAccountBalanceAndValue
cell->setData(QVariant(account.id()), (int)Role::ID);
cell->setData(QVariant(account.value("PreferredAccount") == QLatin1String("Yes")), (int)Role::Favorite);
cell->setData(QVariant(QIcon(account.accountPixmap(m_reconciledAccount.id().isEmpty() ? false : account.id() == m_reconciledAccount.id()))), Qt::DecorationRole);
cell->setData(MyMoneyFile::instance()->accountToCategory(account.id(), true), (int)Role::FullName);
cell->setData(font, Qt::FontRole);
}
// Type
if (columns.contains(Column::Type)) {
colNum = m_columns.indexOf(Column::Type);
if (colNum != -1) {
getCell(colNum);
cell->setData(account.accountTypeToString(account.accountType()), Qt::DisplayRole);
cell->setData(font, Qt::FontRole);
}
}
// Account's number
if (columns.contains(Column::AccountNumber)) {
colNum = m_columns.indexOf(Column::AccountNumber);
if (colNum != -1) {
getCell(colNum);
cell->setData(account.number(), Qt::DisplayRole);
cell->setData(font, Qt::FontRole);
}
}
// Account's sort code
if (columns.contains(Column::AccountSortCode)) {
colNum = m_columns.indexOf(Column::AccountSortCode);
if (colNum != -1) {
getCell(colNum);
cell->setData(account.value("iban"), Qt::DisplayRole);
cell->setData(font, Qt::FontRole);
}
}
const auto checkMark = QIcon::fromTheme(g_Icons[Icon::DialogOK]);
switch (account.accountType()) {
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::Liability:
+ case Account::Income:
+ case Account::Expense:
+ case Account::Asset:
+ case Account::Liability:
// Tax
if (columns.contains(Column::Tax)) {
colNum = m_columns.indexOf(Column::Tax);
if (colNum != -1) {
getCell(colNum);
if (account.value("Tax").toLower() == "yes")
cell->setData(checkMark, Qt::DecorationRole);
else
cell->setData(QIcon(), Qt::DecorationRole);
}
}
// VAT Account
if (columns.contains(Column::VAT)) {
colNum = m_columns.indexOf(Column::VAT);
if (colNum != -1) {
getCell(colNum);
if (!account.value("VatAccount").isEmpty()) {
const auto vatAccount = MyMoneyFile::instance()->account(account.value("VatAccount"));
cell->setData(vatAccount.name(), Qt::DisplayRole);
cell->setData(QVariant(Qt::AlignLeft | Qt::AlignVCenter), Qt::TextAlignmentRole);
// VAT Rate
} else if (!account.value("VatRate").isEmpty()) {
const auto vatRate = MyMoneyMoney(account.value("VatRate")) * MyMoneyMoney(100, 1);
cell->setData(QString::fromLatin1("%1 %").arg(vatRate.formatMoney(QString(), 1)), Qt::DisplayRole);
cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
} else {
cell->setData(QString(), Qt::DisplayRole);
}
}
}
// CostCenter
if (columns.contains(Column::CostCenter)) {
colNum = m_columns.indexOf(Column::CostCenter);
if (colNum != -1) {
getCell(colNum);
if (account.isCostCenterRequired())
cell->setData(checkMark, Qt::DecorationRole);
else
cell->setData(QIcon(), Qt::DecorationRole);
}
}
break;
default:
break;
}
// balance and value
setAccountBalanceAndValue(node, row, account, columns);
}
void setInstitutionTotalValue(QStandardItem *node, const int row)
{
const auto colInstitution = m_columns.indexOf(Column::Account);
auto itInstitution = node->child(row, colInstitution);
const auto valInstitution = childrenTotalValue(itInstitution, true);
itInstitution->setData(QVariant::fromValue(valInstitution ), (int)Role::TotalValue);
const auto colTotalValue = m_columns.indexOf(Column::TotalValue);
if (colTotalValue == -1)
return;
auto cell = node->child(row, colTotalValue);
if (!cell) {
cell = new QStandardItem;
node->setChild(row, colTotalValue, cell);
}
QColor color;
if (valInstitution.isNegative())
color = KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative);
else
color = KColorScheme(QPalette::Active).foreground(KColorScheme::NormalText).color();
cell->setData(QVariant(color), Qt::ForegroundRole);
cell->setData(QVariant(itInstitution->data(Qt::FontRole).value<QFont>()), Qt::FontRole);
cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
cell->setData(MyMoneyUtils::formatMoney(valInstitution, m_file->baseCurrency()), Qt::DisplayRole);
}
void setAccountBalanceAndValue(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList<Column> &columns)
{
QStandardItem *cell;
auto getCell = [&, row](auto column)
{
cell = node->child(row, column);
if (!cell) {
cell = new QStandardItem;
node->setChild(row, column, cell);
}
};
// setting account column
auto colNum = m_columns.indexOf(Column::Account);
if (colNum == -1)
return;
getCell(colNum);
MyMoneyMoney accountBalance, accountValue, accountTotalValue;
if (columns.contains(Column::Account)) { // update values only when requested
accountBalance = balance(account);
accountValue = value(account, accountBalance);
accountTotalValue = childrenTotalValue(cell) + accountValue;
cell->setData(QVariant::fromValue(account), (int)Role::Account);
cell->setData(QVariant::fromValue(accountBalance), (int)Role::Balance);
cell->setData(QVariant::fromValue(accountValue), (int)Role::Value);
cell->setData(QVariant::fromValue(accountTotalValue), (int)Role::TotalValue);
} else { // otherwise save up on tedious calculations
accountBalance = cell->data((int)Role::Balance).value<MyMoneyMoney>();
accountValue = cell->data((int)Role::Value).value<MyMoneyMoney>();
accountTotalValue = cell->data((int)Role::TotalValue).value<MyMoneyMoney>();
}
const auto font = QVariant(cell->data(Qt::FontRole).value<QFont>());
const auto alignment = QVariant(Qt::AlignRight | Qt::AlignVCenter);
// setting total balance column
if (columns.contains(Column::TotalBalance)) {
colNum = m_columns.indexOf(Column::TotalBalance);
if (colNum != -1) {
const auto accountBalanceStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountBalance, m_file->security(account.currencyId())));
getCell(colNum);
// only show the balance, if its a different security/currency
if (m_file->security(account.currencyId()) != m_file->baseCurrency()) {
cell->setData(accountBalanceStr, Qt::DisplayRole);
}
cell->setData(font, Qt::FontRole);
cell->setData(alignment, Qt::TextAlignmentRole);
}
}
// setting posted value column
if (columns.contains(Column::PostedValue)) {
colNum = m_columns.indexOf(Column::PostedValue);
if (colNum != -1) {
const auto accountValueStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountValue, m_file->baseCurrency()));
getCell(colNum);
cell->setData(accountValueStr, Qt::DisplayRole);
cell->setData(font, Qt::FontRole);
cell->setData(alignment, Qt::TextAlignmentRole);
}
}
// setting total value column
if (columns.contains(Column::TotalValue)) {
colNum = m_columns.indexOf(Column::TotalValue);
if (colNum != -1) {
const auto accountTotalValueStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountTotalValue, m_file->baseCurrency()));
getCell(colNum);
QColor color;
if (accountTotalValue.isNegative())
color = KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative);
else
color = KColorScheme(QPalette::Active).foreground(KColorScheme::NormalText).color();
cell->setData(accountTotalValueStr, Qt::DisplayRole);
cell->setData(font, Qt::FontRole);
cell->setData(QVariant(color), Qt::ForegroundRole);
cell->setData(alignment, Qt::TextAlignmentRole);
}
}
}
/**
* Compute the balance of the given account.
*
* @param account The account for which the balance is being computed.
*/
MyMoneyMoney balance(const MyMoneyAccount &account)
{
MyMoneyMoney balance;
// a closed account has a zero balance by definition
if (!account.isClosed()) {
// account.balance() is not compatable with stock accounts
if (account.isInvest())
balance = m_file->balance(account.id());
else
balance = account.balance();
}
// for income and liability accounts, we reverse the sign
switch (account.accountGroup()) {
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Equity:
+ case Account::Income:
+ case Account::Liability:
+ case Account::Equity:
balance = -balance;
break;
default:
break;
}
return balance;
}
/**
* Compute the value of the given account using the provided balance.
* The value is defined as the balance of the account converted to the base currency.
*
* @param account The account for which the value is being computed.
* @param balance The balance which should be used.
*
* @see balance
*/
MyMoneyMoney value(const MyMoneyAccount &account, const MyMoneyMoney &balance)
{
if (account.isClosed())
return MyMoneyMoney();
QList<MyMoneyPrice> prices;
MyMoneySecurity security = m_file->baseCurrency();
try {
if (account.isInvest()) {
security = m_file->security(account.currencyId());
prices += m_file->price(account.currencyId(), security.tradingCurrency());
if (security.tradingCurrency() != m_file->baseCurrency().id()) {
MyMoneySecurity sec = m_file->security(security.tradingCurrency());
prices += m_file->price(sec.id(), m_file->baseCurrency().id());
}
} else if (account.currencyId() != m_file->baseCurrency().id()) {
security = m_file->security(account.currencyId());
prices += m_file->price(account.currencyId(), m_file->baseCurrency().id());
}
} catch (const MyMoneyException &e) {
qDebug() << Q_FUNC_INFO << " caught exception while adding " << account.name() << "[" << account.id() << "]: " << e.what();
}
MyMoneyMoney value = balance;
{
QList<MyMoneyPrice>::const_iterator it_p;
QString security = account.currencyId();
for (it_p = prices.constBegin(); it_p != prices.constEnd(); ++it_p) {
value = (value * (MyMoneyMoney::ONE / (*it_p).rate(security))).convertPrecision(m_file->security(security).pricePrecision());
if ((*it_p).from() == security)
security = (*it_p).to();
else
security = (*it_p).from();
}
value = value.convert(m_file->baseCurrency().smallestAccountFraction());
}
return value;
}
/**
* Compute the total value of the child accounts of the given account.
* Note that the value of the current account is not in this sum. Also,
* before calling this function, the caller must make sure that the values
* of all sub-account must be already in the model in the @ref Role::Value.
*
* @param index The index of the account in the model.
* @see value
*/
MyMoneyMoney childrenTotalValue(const QStandardItem *node, const bool isInstitutionsModel = false)
{
MyMoneyMoney totalValue;
if (!node)
return totalValue;
for (auto i = 0; i < node->rowCount(); ++i) {
const auto childNode = node->child(i, (int)Column::Account);
if (childNode->hasChildren())
totalValue += childrenTotalValue(childNode, isInstitutionsModel);
const auto data = childNode->data((int)Role::Value);
if (data.isValid()) {
auto value = data.value<MyMoneyMoney>();
if (isInstitutionsModel) {
const auto account = childNode->data((int)Role::Account).value<MyMoneyAccount>();
- if (account.accountGroup() == MyMoneyAccount::Liability)
+ if (account.accountGroup() == Account::Liability)
value = -value;
}
totalValue += value;
}
}
return totalValue;
}
/**
* Function to get the item from an account id.
*
* @param parent The parent to localize the search in the child items of this parameter.
* @param accountId Search based on this parameter.
*
* @return The item corresponding to the given account id, NULL if the account was not found.
*/
QStandardItem *itemFromAccountId(QStandardItem *parent, const QString &accountId) {
auto const model = parent->model();
const auto list = model->match(model->index(0, 0, parent->index()), (int)Role::ID, QVariant(accountId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
if (!list.isEmpty())
return model->itemFromIndex(list.front());
// TODO: if not found at this item search for it in the model and if found reparent it.
return nullptr;
}
/**
* Function to get the item from an account id without knowing it's parent item.
* Note that for the accounts which have two items in the model (favorite accounts)
* the account item which is not the child of the favorite accounts item is always returned.
*
* @param model The model in which to search.
* @param accountId Search based on this parameter.
*
* @return The item corresponding to the given account id, NULL if the account was not found.
*/
QStandardItem *itemFromAccountId(QStandardItemModel *model, const QString &accountId)
{
const auto list = model->match(model->index(0, 0), (int)Role::ID, QVariant(accountId), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
foreach (const QModelIndex &index, list) {
// always return the account which is not the child of the favorite accounts item
if (index.parent().data((int)Role::ID).toString() != AccountsModel::favoritesAccountId)
return model->itemFromIndex(index);
}
return nullptr;
}
/**
* Used to load the accounts data.
*/
MyMoneyFile *m_file;
/**
* Used to emit the @ref netWorthChanged signal.
*/
MyMoneyMoney m_lastNetWorth;
/**
* Used to emit the @ref profitChanged signal.
*/
MyMoneyMoney m_lastProfit;
/**
* Used to set the reconciliation flag.
*/
MyMoneyAccount m_reconciledAccount;
QList<Column> m_columns;
static const QString m_accountsModelConfGroup;
static const QString m_accountsModelColumnSelection;
};
const QString AccountsModel::Private::m_accountsModelConfGroup = QStringLiteral("AccountsModel");
const QString AccountsModel::Private::m_accountsModelColumnSelection = QStringLiteral("ColumnSelection");
const QString AccountsModel::favoritesAccountId(QStringLiteral("Favorites"));
/**
* The constructor is private so that only the @ref Models object can create such an object.
*/
AccountsModel::AccountsModel(QObject *parent /*= 0*/)
: QStandardItemModel(parent), d(new Private)
{
init();
}
AccountsModel::AccountsModel(Private* const priv, QObject *parent /*= 0*/)
: QStandardItemModel(parent), d(priv)
{
init();
}
AccountsModel::~AccountsModel()
{
delete d;
}
void AccountsModel::init()
{
QStringList headerLabels;
foreach (const auto column, d->m_columns)
headerLabels.append(getHeaderName(column));
setHorizontalHeaderLabels(headerLabels);
}
/**
* Perform the initial load of the model data
* from the @ref MyMoneyFile.
*
*/
void AccountsModel::load()
{
this->blockSignals(true);
QStandardItem *rootItem = invisibleRootItem();
QFont font;
font.setBold(true);
// adding favourite accounts node
auto favoriteAccountsItem = new QStandardItem();
favoriteAccountsItem->setEditable(false);
rootItem->appendRow(favoriteAccountsItem);
{
QMap<int, QVariant> itemData;
itemData[Qt::DisplayRole] = itemData[Qt::EditRole] = itemData[(int)Role::FullName] = i18n("Favorites");
itemData[Qt::FontRole] = font;
itemData[Qt::DecorationRole] = QIcon::fromTheme(g_Icons.value(Icon::ViewBankAccount));
itemData[(int)Role::ID] = favoritesAccountId;
itemData[(int)Role::DisplayOrder] = 0;
this->setItemData(favoriteAccountsItem->index(), itemData);
}
// adding account categories (asset, liability, etc.) node
- QVector <MyMoneyAccount::_accountTypeE> categories {
- MyMoneyAccount::Asset, MyMoneyAccount::Liability,
- MyMoneyAccount::Income, MyMoneyAccount::Expense,
- MyMoneyAccount::Equity
+ QVector <Account> categories {
+ Account::Asset, Account::Liability,
+ Account::Income, Account::Expense,
+ Account::Equity
};
foreach (const auto category, categories) {
MyMoneyAccount account;
QString accountName;
int displayOrder;
switch (category) {
- case MyMoneyAccount::Asset:
+ case Account::Asset:
// Asset accounts
account = d->m_file->asset();
accountName = i18n("Asset accounts");
displayOrder = 1;
break;
- case MyMoneyAccount::Liability:
+ case Account::Liability:
// Liability accounts
account = d->m_file->liability();
accountName = i18n("Liability accounts");
displayOrder = 2;
break;
- case MyMoneyAccount::Income:
+ case Account::Income:
// Income categories
account = d->m_file->income();
accountName = i18n("Income categories");
displayOrder = 3;
break;
- case MyMoneyAccount::Expense:
+ case Account::Expense:
// Expense categories
account = d->m_file->expense();
accountName = i18n("Expense categories");
displayOrder = 4;
break;
- case MyMoneyAccount::Equity:
+ case Account::Equity:
// Equity accounts
account = d->m_file->equity();
accountName = i18n("Equity accounts");
displayOrder = 5;
break;
default:
continue;
}
auto accountsItem = new QStandardItem(accountName);
accountsItem->setEditable(false);
rootItem->appendRow(accountsItem);
{
QMap<int, QVariant> itemData;
itemData[Qt::DisplayRole] = accountName;
itemData[(int)Role::FullName] = itemData[Qt::EditRole] = QVariant::fromValue(MyMoneyFile::instance()->accountToCategory(account.id(), true));
itemData[Qt::FontRole] = font;
itemData[(int)Role::DisplayOrder] = displayOrder;
this->setItemData(accountsItem->index(), itemData);
}
// adding accounts (specific bank/investment accounts) belonging to given accounts category
const auto accountsStr = account.accountList();
foreach (const auto accStr, accountsStr) {
const auto acc = d->m_file->account(accStr);
auto item = new QStandardItem(acc.name());
accountsItem->appendRow(item);
item->setEditable(false);
auto subaccountsStr = acc.accountList();
// filter out stocks with zero balance if requested by user
for (auto subaccStr = subaccountsStr.begin(); subaccStr != subaccountsStr.end();) {
const auto subacc = d->m_file->account(*subaccStr);
if (subacc.isInvest() && KMyMoneyGlobalSettings::hideZeroBalanceEquities() && subacc.balance().isZero())
subaccStr = subaccountsStr.erase(subaccStr);
else
++subaccStr;
}
// adding subaccounts (e.g. stocks under given investment account) belonging to given account
d->loadSubaccounts(item, favoriteAccountsItem, subaccountsStr);
const auto row = item->row();
d->setAccountData(accountsItem, row, acc, d->m_columns);
d->loadPreferredAccount(acc, accountsItem, row, favoriteAccountsItem);
}
d->setAccountData(rootItem, accountsItem->row(), account, d->m_columns);
}
checkNetWorth();
checkProfit();
this->blockSignals(false);
}
QModelIndex AccountsModel::accountById(const QString& id) const
{
QModelIndexList accountList = match(index(0, 0),
(int)Role::ID,
id,
1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive));
if(accountList.count() == 1) {
return accountList.first();
}
return QModelIndex();
}
QList<Column> *AccountsModel::getColumns()
{
return &d->m_columns;
}
void AccountsModel::setColumnVisibility(const Column column, const bool show)
{
const auto ixCol = d->m_columns.indexOf(column); // get column index in our column's map
if (!show && ixCol != -1) { // start removing column row by row from bottom to up
d->m_columns.removeOne(column); // remove it from our column's map
blockSignals(true); // block signals to not emit resources consuming dataChanged
for (auto i = 0; i < rowCount(); ++i) {
// recursive lambda function to remove cell belonging to unwanted column from all rows
auto removeCellFromRow = [=](auto &&self, QStandardItem *item) -> bool {
for(auto j = 0; j < item->rowCount(); ++j) {
auto childItem = item->child(j);
if (childItem->hasChildren())
self(self, childItem);
childItem->removeColumn(ixCol);
}
return true;
};
auto topItem = item(i);
if (topItem->hasChildren())
removeCellFromRow(removeCellFromRow, topItem);
topItem->removeColumn(ixCol);
}
blockSignals(false); // unblock signals, so model can update itself with new column
removeColumn(ixCol); // remove column from invisible root item which triggers model's view update
} else if (show && ixCol == -1) { // start inserting columns row by row from up to bottom (otherwise columns will be inserted automatically)
auto model = qobject_cast<InstitutionsModel *>(this);
const auto isInstitutionsModel = model ? true : false; // if it's institution's model, then don't set any data on institution nodes
auto newColPos = 0;
for(; newColPos < d->m_columns.count(); ++newColPos) {
if (d->m_columns.at(newColPos) > column)
break;
}
d->m_columns.insert(newColPos, column); // insert columns according to enum order for cleanliness
insertColumn(newColPos);
setHorizontalHeaderItem(newColPos, new QStandardItem(getHeaderName(column)));
blockSignals(true);
for (auto i = 0; i < rowCount(); ++i) {
// recursive lambda function to remove cell belonging to unwanted column from all rows
auto addCellToRow = [&, newColPos](auto &&self, QStandardItem *item) -> bool {
for(auto j = 0; j < item->rowCount(); ++j) {
auto childItem = item->child(j);
childItem->insertColumns(newColPos, 1);
if (childItem->hasChildren())
self(self, childItem);
this->d->setAccountData(item, j, childItem->data((int)Role::Account).value<MyMoneyAccount>(), QList<Column> {column});
}
return true;
};
auto topItem = item(i);
topItem->insertColumns(newColPos, 1);
if (topItem->hasChildren())
addCellToRow(addCellToRow, topItem);
if (isInstitutionsModel)
d->setInstitutionTotalValue(invisibleRootItem(), i);
else if (i !=0) // favourites node doesn't play well here, so exclude it from update
d->setAccountData(invisibleRootItem(), i, topItem->data((int)Role::Account).value<MyMoneyAccount>(), QList<Column> {column});
}
blockSignals(false);
}
}
QString AccountsModel::getHeaderName(const Column column)
{
switch(column) {
case Column::Account:
return i18n("Account");
case Column::Type:
return i18n("Type");
case Column::Tax:
return i18nc("Column heading for category in tax report", "Tax");
case Column::VAT:
return i18nc("Column heading for VAT category", "VAT");
case Column::CostCenter:
return i18nc("Column heading for Cost Center", "CC");
case Column::TotalBalance:
return i18n("Total Balance");
case Column::PostedValue:
return i18n("Posted Value");
case Column::TotalValue:
return i18n("Total Value");
case Column::AccountNumber:
return i18n("Number");
case Column::AccountSortCode:
return i18nc("IBAN, SWIFT, etc.", "Sort Code");
default:
return QString();
}
}
/**
* Check if netWorthChanged should be emitted.
*/
void AccountsModel::checkNetWorth()
{
// compute the net woth
QModelIndexList assetList = match(index(0, 0),
(int)Role::ID,
MyMoneyFile::instance()->asset().id(),
1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
QModelIndexList liabilityList = match(index(0, 0),
(int)Role::ID,
MyMoneyFile::instance()->liability().id(),
1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
MyMoneyMoney netWorth;
if (!assetList.isEmpty() && !liabilityList.isEmpty()) {
const auto assetValue = data(assetList.front(), (int)Role::TotalValue);
const auto liabilityValue = data(liabilityList.front(), (int)Role::TotalValue);
if (assetValue.isValid() && liabilityValue.isValid())
netWorth = assetValue.value<MyMoneyMoney>() - liabilityValue.value<MyMoneyMoney>();
}
if (d->m_lastNetWorth != netWorth) {
d->m_lastNetWorth = netWorth;
emit netWorthChanged(d->m_lastNetWorth);
}
}
/**
* Check if profitChanged should be emitted.
*/
void AccountsModel::checkProfit()
{
// compute the profit
const auto incomeList = match(index(0, 0),
(int)Role::ID,
MyMoneyFile::instance()->income().id(),
1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
const auto expenseList = match(index(0, 0),
(int)Role::ID,
MyMoneyFile::instance()->expense().id(),
1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
MyMoneyMoney profit;
if (!incomeList.isEmpty() && !expenseList.isEmpty()) {
const auto incomeValue = data(incomeList.front(), (int)Role::TotalValue);
const auto expenseValue = data(expenseList.front(), (int)Role::TotalValue);
if (incomeValue.isValid() && expenseValue.isValid())
profit = incomeValue.value<MyMoneyMoney>() - expenseValue.value<MyMoneyMoney>();
}
if (d->m_lastProfit != profit) {
d->m_lastProfit = profit;
emit profitChanged(d->m_lastProfit);
}
}
MyMoneyMoney AccountsModel::accountValue(const MyMoneyAccount &account, const MyMoneyMoney &balance)
{
return d->value(account, balance);
}
/**
* This slot should be connected so that the model will be notified which account is being reconciled.
*/
void AccountsModel::slotReconcileAccount(const MyMoneyAccount &account, const QDate &reconciliationDate, const MyMoneyMoney &endingBalance)
{
Q_UNUSED(reconciliationDate)
Q_UNUSED(endingBalance)
if (d->m_reconciledAccount.id() != account.id()) {
// first clear the flag of the old reconciliation account
if (!d->m_reconciledAccount.id().isEmpty()) {
const auto list = match(index(0, 0), (int)Role::ID, QVariant(d->m_reconciledAccount.id()), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
foreach (const auto index, list)
setData(index, QVariant(QIcon(account.accountPixmap(false))), Qt::DecorationRole);
}
// then set the reconciliation flag of the new reconciliation account
const auto list = match(index(0, 0), (int)Role::ID, QVariant(account.id()), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
foreach (const auto index, list)
setData(index, QVariant(QIcon(account.accountPixmap(true))), Qt::DecorationRole);
d->m_reconciledAccount = account;
}
}
/**
* Notify the model that an object has been added. An action is performed only if the object is an account.
*
*/
-void AccountsModel::slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj)
+void AccountsModel::slotObjectAdded(File::Object objType, const MyMoneyObject * const obj)
{
- if (objType != MyMoneyFile::notifyAccount)
+ if (objType != File::Object::Account)
return;
const MyMoneyAccount * const account = dynamic_cast<const MyMoneyAccount * const>(obj);
if (!account)
return;
auto favoriteAccountsItem = d->itemFromAccountId(this, favoritesAccountId);
auto parentAccountItem = d->itemFromAccountId(this, account->parentAccountId());
auto item = d->itemFromAccountId(parentAccountItem, account->id());
if (!item) {
item = new QStandardItem(account->name());
parentAccountItem->appendRow(item);
item->setEditable(false);
}
// load the sub-accounts if there are any - there could be sub accounts if this is an add operation
// that was triggered in slotObjectModified on an already existing account which went trough a hierarchy change
d->loadSubaccounts(item, favoriteAccountsItem, account->accountList());
const auto row = item->row();
d->setAccountData(parentAccountItem, row, *account, d->m_columns);
d->loadPreferredAccount(*account, parentAccountItem, row, favoriteAccountsItem);
checkNetWorth();
checkProfit();
}
/**
* Notify the model that an object has been modified. An action is performed only if the object is an account.
*
*/
-void AccountsModel::slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj)
+void AccountsModel::slotObjectModified(File::Object objType, const MyMoneyObject * const obj)
{
- if (objType != MyMoneyFile::notifyAccount)
+ if (objType != File::Object::Account)
return;
const MyMoneyAccount * const account = dynamic_cast<const MyMoneyAccount * const>(obj);
if (!account)
return;
auto favoriteAccountsItem = d->itemFromAccountId(this, favoritesAccountId);
auto accountItem = d->itemFromAccountId(this, account->id());
const auto oldAccount = accountItem->data((int)Role::Account).value<MyMoneyAccount>();
if (oldAccount.parentAccountId() == account->parentAccountId()) {
// the hierarchy did not change so update the account data
auto parentAccountItem = accountItem->parent();
if (!parentAccountItem)
parentAccountItem = this->invisibleRootItem();
const auto row = accountItem->row();
d->setAccountData(parentAccountItem, row, *account, d->m_columns);
// and the child of the favorite item if the account is a favorite account or it's favorite status has just changed
auto favItem = d->itemFromAccountId(favoriteAccountsItem, account->id());
if (account->value("PreferredAccount") == QLatin1String("Yes"))
d->loadPreferredAccount(*account, parentAccountItem, row, favoriteAccountsItem);
else if (favItem)
favoriteAccountsItem->removeRow(favItem->row()); // it's not favorite anymore
} else {
// this means that the hierarchy was changed - simulate this with a remove followed by and add operation
- slotObjectRemoved(MyMoneyFile::notifyAccount, oldAccount.id());
- slotObjectAdded(MyMoneyFile::notifyAccount, obj);
+ slotObjectRemoved(File::Object::Account, oldAccount.id());
+ slotObjectAdded(File::Object::Account, obj);
}
checkNetWorth();
checkProfit();
}
/**
* Notify the model that an object has been removed. An action is performed only if the object is an account.
*
*/
-void AccountsModel::slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id)
+void AccountsModel::slotObjectRemoved(File::Object objType, const QString& id)
{
- if (objType != MyMoneyFile::notifyAccount)
+ if (objType != File::Object::Account)
return;
auto list = match(index(0, 0), (int)Role::ID, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive));
foreach (const auto index, list)
removeRow(index.row(), index.parent());
checkNetWorth();
checkProfit();
}
/**
* Notify the model that the account balance has been changed.
*/
void AccountsModel::slotBalanceOrValueChanged(const MyMoneyAccount &account)
{
auto itParent = d->itemFromAccountId(this, account.id()); // get node of account in model
auto isTopLevel = false; // it could be top-level but we don't know it yet
while (itParent && !isTopLevel) { // loop in which we set total values and balances from the bottom to the top
auto itCurrent = itParent;
auto accCurrent = &d->m_file->account(itCurrent->data((int)Role::Account).value<MyMoneyAccount>().id());
if (accCurrent->id().isEmpty()) { // this is institution
d->setInstitutionTotalValue(invisibleRootItem(), itCurrent->row());
break; // it's top-level node so nothing above that;
}
itParent = itCurrent->parent();
if (!itParent) {
itParent = this->invisibleRootItem();
isTopLevel = true;
}
d->setAccountBalanceAndValue(itParent, itCurrent->row(), *accCurrent, d->m_columns);
}
checkNetWorth();
checkProfit();
}
/**
* The pimpl of the @ref InstitutionsModel derived from the pimpl of the @ref AccountsModel.
*/
class InstitutionsModel::InstitutionsPrivate : public AccountsModel::Private
{
public:
/**
* Function to get the institution item from an institution id.
*
* @param model The model in which to look for the item.
* @param institutionId Search based on this parameter.
*
* @return The item corresponding to the given institution id, NULL if the institution was not found.
*/
QStandardItem *institutionItemFromId(QStandardItemModel *model, const QString &institutionId) {
const auto list = model->match(model->index(0, 0), (int)Role::ID, QVariant(institutionId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
if (!list.isEmpty())
return model->itemFromIndex(list.front());
return nullptr; // this should rarely fail as we add all institutions early on
}
/**
* Function to add the account item to it's corresponding institution item.
*
* @param model The model where to add the item.
* @param account The account for which to create the item.
*
*/
void loadInstitution(QStandardItemModel *model, const MyMoneyAccount &account) {
if (!account.isAssetLiability() && !account.isInvest())
return;
// we've got account but don't know under which institution it should be added, so we find it out
auto idInstitution = account.institutionId();
if (account.isInvest()) { // if it's stock account then...
const auto investmentAccount = m_file->account(account.parentAccountId()); // ...get investment account it's under and...
idInstitution = investmentAccount.institutionId(); // ...get institution from investment account
}
auto itInstitution = institutionItemFromId(model, idInstitution);
auto itAccount = itemFromAccountId(itInstitution, account.id()); // check if account already exists under institution
// only stock accounts are added to their parent in the institutions view
// this makes hierarchy maintenance a lot easier since the stock accounts
// are the only ones that always have the same institution as their parent
auto itInvestmentAccount = account.isInvest() ? itemFromAccountId(itInstitution, account.parentAccountId()) : nullptr;
if (!itAccount) {
itAccount = new QStandardItem(account.name());
if (itInvestmentAccount) // stock account nodes go under investment account nodes and...
itInvestmentAccount->appendRow(itAccount);
else if (itInstitution) // ...the rest goes under institution's node
itInstitution->appendRow(itAccount);
else
return;
itAccount->setEditable(false);
}
if (itInvestmentAccount) {
setAccountData(itInvestmentAccount, itAccount->row(), account, m_columns); // set data for stock account node
setAccountData(itInstitution, itInvestmentAccount->row(), m_file->account(account.parentAccountId()), m_columns); // set data for investment account node
} else if (itInstitution) {
setAccountData(itInstitution, itAccount->row(), account, m_columns);
}
}
/**
* Function to add an institution item to the model.
*
* @param model The model in which to add the item.
* @param institution The institution object which should be represented by the item.
*
*/
void addInstitutionItem(QStandardItemModel *model, const MyMoneyInstitution &institution) {
QFont font;
font.setBold(true);
auto itInstitution = new QStandardItem(QIcon::fromTheme(g_Icons.value(Icon::ViewInstitutions)), institution.name());
itInstitution->setFont(font);
itInstitution->setData(QVariant::fromValue(MyMoneyMoney()), (int)Role::TotalValue);
itInstitution->setData(institution.id(), (int)Role::ID);
itInstitution->setData(QVariant::fromValue(institution), (int)Role::Account);
itInstitution->setData(6, (int)Role::DisplayOrder);
itInstitution->setEditable(false);
model->invisibleRootItem()->appendRow(itInstitution);
setInstitutionTotalValue(model->invisibleRootItem(), itInstitution->row());
}
};
/**
* The institution model contains the accounts grouped by institution.
*
*/
InstitutionsModel::InstitutionsModel(QObject *parent /*= 0*/)
: AccountsModel(new InstitutionsPrivate, parent)
{
}
/**
* Perform the initial load of the model data
* from the @ref MyMoneyFile.
*
*/
void InstitutionsModel::load()
{
// create items for all the institutions
QList<MyMoneyInstitution> institutionList;
d->m_file->institutionList(institutionList);
MyMoneyInstitution none;
none.setName(i18n("Accounts with no institution assigned"));
institutionList.append(none);
auto modelUtils = static_cast<InstitutionsPrivate *>(d);
foreach (const auto institution, institutionList) // add all known institutions as top-level nodes
modelUtils->addInstitutionItem(this, institution);
QList<MyMoneyAccount> accountsList;
QList<MyMoneyAccount> stocksList;
d->m_file->accountList(accountsList);
foreach (const auto account, accountsList) { // add account nodes under institution nodes...
if (account.isInvest()) // ...but wait with stocks until investment accounts appear
stocksList.append(account);
else
modelUtils->loadInstitution(this, account);
}
foreach (const auto stock, stocksList) {
if (!(KMyMoneyGlobalSettings::hideZeroBalanceEquities() && stock.balance().isZero()))
modelUtils->loadInstitution(this, stock);
}
for (auto i = 0 ; i < rowCount(); ++i)
d->setInstitutionTotalValue(invisibleRootItem(), i);
}
/**
* Notify the model that an object has been added. An action is performed only if the object is an account or an institution.
*
*/
-void InstitutionsModel::slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj)
+void InstitutionsModel::slotObjectAdded(File::Object objType, const MyMoneyObject * const obj)
{
auto modelUtils = static_cast<InstitutionsPrivate *>(d);
- if (objType == MyMoneyFile::notifyInstitution) {
+ if (objType == File::Object::Institution) {
// if an institution was added then add the item which will represent it
const MyMoneyInstitution * const institution = dynamic_cast<const MyMoneyInstitution * const>(obj);
if (!institution)
return;
modelUtils->addInstitutionItem(this, *institution);
}
- if (objType != MyMoneyFile::notifyAccount)
+ if (objType != File::Object::Account)
return;
// if an account was added then add the item which will represent it only for real accounts
const MyMoneyAccount * const account = dynamic_cast<const MyMoneyAccount * const>(obj);
// nothing to do for root accounts and categories
if (!account || account->parentAccountId().isEmpty() || account->isIncomeExpense())
return;
// load the account into the institution
modelUtils->loadInstitution(this, *account);
// load the investment sub-accounts if there are any - there could be sub-accounts if this is an add operation
// that was triggered in slotObjectModified on an already existing account which went trough a hierarchy change
if (!account->accountList().isEmpty()) {
QList<MyMoneyAccount> subAccounts;
d->m_file->accountList(subAccounts, account->accountList());
foreach (const auto subAccount, subAccounts) {
if (subAccount.isInvest()) {
modelUtils->loadInstitution(this, subAccount);
}
}
}
}
/**
* Notify the model that an object has been modified. An action is performed only if the object is an account or an institution.
*
*/
-void InstitutionsModel::slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj)
+void InstitutionsModel::slotObjectModified(File::Object objType, const MyMoneyObject * const obj)
{
- if (objType == MyMoneyFile::notifyInstitution) {
+ if (objType == File::Object::Institution) {
// if an institution was modified then modify the item which represents it
const MyMoneyInstitution * const institution = dynamic_cast<const MyMoneyInstitution * const>(obj);
if (!institution)
return;
auto institutionItem = static_cast<InstitutionsPrivate *>(d)->institutionItemFromId(this, institution->id());
institutionItem->setData(institution->name(), Qt::DisplayRole);
institutionItem->setData(QVariant::fromValue(*institution), (int)Role::Account);
institutionItem->setIcon(institution->pixmap());
}
- if (objType != MyMoneyFile::notifyAccount)
+ if (objType != File::Object::Account)
return;
// if an account was modified then modify the item which represents it
const MyMoneyAccount * const account = dynamic_cast<const MyMoneyAccount * const>(obj);
// nothing to do for root accounts, categories and equity accounts since they don't have a representation in this model
- if (!account || account->parentAccountId().isEmpty() || account->isIncomeExpense() || account->accountType() == MyMoneyAccount::Equity)
+ if (!account || account->parentAccountId().isEmpty() || account->isIncomeExpense() || account->accountType() == Account::Equity)
return;
auto accountItem = d->itemFromAccountId(this, account->id());
const auto oldAccount = accountItem->data((int)Role::Account).value<MyMoneyAccount>();
if (oldAccount.institutionId() == account->institutionId()) {
// the hierarchy did not change so update the account data
d->setAccountData(accountItem->parent(), accountItem->row(), *account, d->m_columns);
} else {
// this means that the hierarchy was changed - simulate this with a remove followed by and add operation
- slotObjectRemoved(MyMoneyFile::notifyAccount, oldAccount.id());
- slotObjectAdded(MyMoneyFile::notifyAccount, obj);
+ slotObjectRemoved(File::Object::Account, oldAccount.id());
+ slotObjectAdded(File::Object::Account, obj);
}
}
/**
* Notify the model that an object has been removed. An action is performed only if the object is an account or an institution.
*
*/
-void InstitutionsModel::slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id)
+void InstitutionsModel::slotObjectRemoved(File::Object objType, const QString& id)
{
- if (objType == MyMoneyFile::notifyInstitution) {
+ if (objType == File::Object::Institution) {
// if an institution was removed then remove the item which represents it
auto itInstitution = static_cast<InstitutionsPrivate *>(d)->institutionItemFromId(this, id);
if (itInstitution)
removeRow(itInstitution->row(), itInstitution->index().parent());
}
- if (objType != MyMoneyFile::notifyAccount)
+ if (objType != File::Object::Account)
return;
// if an account was removed then remove the item which represents it and recompute the institution's value
auto itAccount = d->itemFromAccountId(this, id);
if (!itAccount)
return; // this could happen if the account isIncomeExpense
const auto account = itAccount->data((int)Role::Account).value<MyMoneyAccount>();
auto itInstitution = d->itemFromAccountId(this, account.institutionId());
AccountsModel::slotObjectRemoved(objType, id);
d->setInstitutionTotalValue(invisibleRootItem(), itInstitution->row());
}
diff --git a/kmymoney/models/accountsmodel.h b/kmymoney/models/accountsmodel.h
index 8d740336f..7628c3e10 100644
--- a/kmymoney/models/accountsmodel.h
+++ b/kmymoney/models/accountsmodel.h
@@ -1,184 +1,186 @@
/***************************************************************************
* Copyright 2010 Cristian Onet onet.cristian@gmail.com *
* Copyright 2017 Łukasz Wojniłowicz 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) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 of the license. *
* *
* 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 <http://www.gnu.org/licenses/> *
***************************************************************************/
#ifndef ACCOUNTSMODEL_H
#define ACCOUNTSMODEL_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QStandardItemModel>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
-#include "mymoneyfile.h"
#include "modelenums.h"
+#include "mymoneyenums.h"
/**
* A model for the accounts.
* This model loads all the accounts from the @ref MyMoneyFile.
* It also computes various data like account balances needed
* in different views. This object should be kept sychronized
* with the data in the @ref MyMoneyFile (this is accomplished
* by the @ref Models object).
*
* @see MyMoneyAccount
* @see MyMoneyFile
*
* @author Cristian Onet 2010
* @author Łukasz Wojniłowicz 2017
*
*/
+class MyMoneyObject;
+class MyMoneyMoney;
class MyMoneyAccount;
class AccountsModel : public QStandardItemModel
{
Q_OBJECT
public:
/**
* The account id used by this model for the 'Favorites' top level item. This can be used to identify that item on the @ref AccountIdRole.
*/
static const QString favoritesAccountId;
~AccountsModel();
/**
* This method must be used to perform the initial load of the model.
*/
void load();
/**
* Compute the value of the given account using the provided balance.
* The value is defined as the balance of the account converted to the base currency.
*
* @param account The account for which the value is being computed.
* @param balance The balance which should be used.
*
* @todo Make this a static or a global function since the object's state has nothing to do with this computation
*/
MyMoneyMoney accountValue(const MyMoneyAccount &account, const MyMoneyMoney &balance);
/**
* This method returns the QModelIndex of the account specified by its @a id. If the
* account was not found, an invalid QModelIndex is returned.
*/
QModelIndex accountById(const QString& id) const;
QList<eAccountsModel::Column> *getColumns();
void setColumnVisibility(const eAccountsModel::Column column, const bool show);
static QString getHeaderName(const eAccountsModel::Column column);
public slots:
void slotReconcileAccount(const MyMoneyAccount &account, const QDate &reconciliationDate, const MyMoneyMoney &endingBalance);
- void slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id);
+ void slotObjectAdded(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void slotObjectModified(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void slotObjectRemoved(eMyMoney::File::Object objType, const QString& id);
void slotBalanceOrValueChanged(const MyMoneyAccount &account);
signals:
/**
* Emit this signal when the net worth based on the value of the loaded accounts is changed.
*/
void netWorthChanged(const MyMoneyMoney &);
/**
* Emit this signal when the profit based on the value of the loaded accounts is changed.
*/
void profitChanged(const MyMoneyMoney &);
private:
AccountsModel(QObject *parent = 0);
void init();
void checkNetWorth();
void checkProfit();
/**
* The copy-constructor is private so that only the @ref Models object can create such an object.
*/
AccountsModel(const AccountsModel&);
AccountsModel& operator=(AccountsModel&);
/**
* Allow only the @ref Models object to create such an object.
*/
friend class Models;
protected:
class Private;
Private* const d;
/**
* This constructor can be used from derived classes in order to use a derived Private class.
*/
AccountsModel(Private* const priv, QObject *parent = 0);
};
/**
* A model for the accounts grouped by institutions. It extends the functionality already present
* in @ref AccountsModel to enable the grouping of the accounts by institutions.
*
* @author Cristian Onet 2011
* @author Łukasz Wojniłowicz 2017
*
*/
class InstitutionsModel : public AccountsModel
{
Q_OBJECT
public:
/**
* This method must be used to perform the initial load of the model.
*/
void load();
public slots:
- void slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id);
+ void slotObjectAdded(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void slotObjectModified(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void slotObjectRemoved(eMyMoney::File::Object objType, const QString& id);
private:
InstitutionsModel(QObject *parent = 0);
/**
* The copy-constructor is private so that only the @ref Models object can create such an object.
*/
InstitutionsModel(const InstitutionsModel&);
InstitutionsModel& operator=(InstitutionsModel&);
/**
* Allow only the @ref Models object to create such an object.
*/
friend class Models;
/**
* The implementation object is derived from the @ref AccountsModel objects implementation object.
*/
class InstitutionsPrivate;
};
#endif
diff --git a/kmymoney/models/accountsproxymodel.cpp b/kmymoney/models/accountsproxymodel.cpp
index 81beeee89..02bd592d8 100644
--- a/kmymoney/models/accountsproxymodel.cpp
+++ b/kmymoney/models/accountsproxymodel.cpp
@@ -1,361 +1,361 @@
/***************************************************************************
* Copyright 2010 Cristian Onet onet.cristian@gmail.com *
* Copyright 2017 Łukasz Wojniłowicz 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) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 of the license. *
* *
* 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 <http://www.gnu.org/licenses/> *
***************************************************************************/
#include "accountsproxymodel.h"
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "modelenums.h"
using namespace eAccountsModel;
/**
* The pimpl.
*/
class MyMoneyInstitution;
class AccountsProxyModel::Private
{
public:
Private() :
m_hideClosedAccounts(true),
m_hideEquityAccounts(true),
m_hideUnusedIncomeExpenseAccounts(false),
m_haveHiddenUnusedIncomeExpenseAccounts(false) {
}
~Private() {
}
- QList<MyMoneyAccount::accountTypeE> m_typeList;
+ QList<eMyMoney::Account> m_typeList;
bool m_hideClosedAccounts;
bool m_hideEquityAccounts;
bool m_hideUnusedIncomeExpenseAccounts;
bool m_haveHiddenUnusedIncomeExpenseAccounts;
};
AccountsProxyModel::AccountsProxyModel(QObject *parent)
: KRecursiveFilterProxyModel(parent), d(new Private)
{
setDynamicSortFilter(true);
setSortLocaleAware(true);
setFilterCaseSensitivity(Qt::CaseInsensitive);
}
AccountsProxyModel::~AccountsProxyModel()
{
delete d;
}
/**
* This function was re-implemented so we could have a special display order (favorites first)
*/
bool AccountsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
if (!left.isValid() || !right.isValid())
return false;
// different sorting based on the column which is being sorted
switch (m_mdlColumns->at(left.column())) {
// for the accounts column sort based on the DisplayOrderRole
case Column::Account: {
const auto leftData = sourceModel()->data(left, (int)Role::DisplayOrder);
const auto rightData = sourceModel()->data(right, (int)Role::DisplayOrder);
if (leftData.toInt() == rightData.toInt()) {
// sort items of the same display order alphabetically
return QSortFilterProxyModel::lessThan(left, right);
}
return leftData.toInt() < rightData.toInt();
}
// the total balance and value columns are sorted based on the value of the account
case Column::TotalBalance:
case Column::TotalValue: {
const auto leftData = sourceModel()->data(sourceModel()->index(left.row(), (int)Column::Account, left.parent()), (int)Role::TotalValue);
const auto rightData = sourceModel()->data(sourceModel()->index(right.row(), (int)Column::Account, right.parent()), (int)Role::TotalValue);
return leftData.value<MyMoneyMoney>() < rightData.value<MyMoneyMoney>();
}
default:
break;
}
return QSortFilterProxyModel::lessThan(left, right);
}
/**
* This function was re-implemented to consider all the filtering aspects that we need in the application.
*/
bool AccountsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
const auto index = sourceModel()->index(source_row, (int)Column::Account, source_parent);
return acceptSourceItem(index) && filterAcceptsRowOrChildRows(source_row, source_parent);
}
/**
* This function implements a recursive matching. It is used to match a row even if it's values
* don't match the current filtering criteria but it has at least one child row that does match.
*/
bool AccountsProxyModel::filterAcceptsRowOrChildRows(int source_row, const QModelIndex &source_parent) const
{
if (QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent))
return true;
const auto index = sourceModel()->index(source_row, (int)Column::Account, source_parent);
for (auto i = 0; i < sourceModel()->rowCount(index); ++i) {
if (filterAcceptsRowOrChildRows(i, index))
return true;
}
return false;
}
/**
* Add the given account group to the filter.
* @param group The account group to be added.
- * @see MyMoneyAccount::accountTypeE
+ * @see eMyMoney::Account
*/
-void AccountsProxyModel::addAccountGroup(const QVector<MyMoneyAccount::_accountTypeE> &groups)
+void AccountsProxyModel::addAccountGroup(const QVector<eMyMoney::Account> &groups)
{
foreach (const auto group, groups) {
switch (group) {
- case MyMoneyAccount::Asset:
- d->m_typeList << MyMoneyAccount::Checkings;
- d->m_typeList << MyMoneyAccount::Savings;
- d->m_typeList << MyMoneyAccount::Cash;
- d->m_typeList << MyMoneyAccount::AssetLoan;
- d->m_typeList << MyMoneyAccount::CertificateDep;
- d->m_typeList << MyMoneyAccount::Investment;
- d->m_typeList << MyMoneyAccount::Stock;
- d->m_typeList << MyMoneyAccount::MoneyMarket;
- d->m_typeList << MyMoneyAccount::Asset;
- d->m_typeList << MyMoneyAccount::Currency;
+ case eMyMoney::Account::Asset:
+ d->m_typeList << eMyMoney::Account::Checkings;
+ d->m_typeList << eMyMoney::Account::Savings;
+ d->m_typeList << eMyMoney::Account::Cash;
+ d->m_typeList << eMyMoney::Account::AssetLoan;
+ d->m_typeList << eMyMoney::Account::CertificateDep;
+ d->m_typeList << eMyMoney::Account::Investment;
+ d->m_typeList << eMyMoney::Account::Stock;
+ d->m_typeList << eMyMoney::Account::MoneyMarket;
+ d->m_typeList << eMyMoney::Account::Asset;
+ d->m_typeList << eMyMoney::Account::Currency;
break;
- case MyMoneyAccount::Liability:
- d->m_typeList << MyMoneyAccount::CreditCard;
- d->m_typeList << MyMoneyAccount::Loan;
- d->m_typeList << MyMoneyAccount::Liability;
+ case eMyMoney::Account::Liability:
+ d->m_typeList << eMyMoney::Account::CreditCard;
+ d->m_typeList << eMyMoney::Account::Loan;
+ d->m_typeList << eMyMoney::Account::Liability;
break;
- case MyMoneyAccount::Income:
- d->m_typeList << MyMoneyAccount::Income;
+ case eMyMoney::Account::Income:
+ d->m_typeList << eMyMoney::Account::Income;
break;
- case MyMoneyAccount::Expense:
- d->m_typeList << MyMoneyAccount::Expense;
+ case eMyMoney::Account::Expense:
+ d->m_typeList << eMyMoney::Account::Expense;
break;
- case MyMoneyAccount::Equity:
- d->m_typeList << MyMoneyAccount::Equity;
+ case eMyMoney::Account::Equity:
+ d->m_typeList << eMyMoney::Account::Equity;
break;
default:
break;
}
}
invalidateFilter();
}
/**
* Add the given account type to the filter.
* @param type The account type to be added.
- * @see MyMoneyAccount::accountTypeE
+ * @see eMyMoney::Account
*/
-void AccountsProxyModel::addAccountType(MyMoneyAccount::accountTypeE type)
+void AccountsProxyModel::addAccountType(eMyMoney::Account type)
{
d->m_typeList << type;
invalidateFilter();
}
/**
* Remove the given account type from the filter.
* @param type The account type to be removed.
- * @see MyMoneyAccount::accountTypeE
+ * @see eMyMoney::Account
*/
-void AccountsProxyModel::removeAccountType(MyMoneyAccount::accountTypeE type)
+void AccountsProxyModel::removeAccountType(eMyMoney::Account type)
{
if (d->m_typeList.removeAll(type) > 0) {
invalidateFilter();
}
}
/**
* Use this to reset the filter.
*/
void AccountsProxyModel::clear()
{
d->m_typeList.clear();
invalidateFilter();
}
/**
* Implementation function that performs the actual filtering.
*/
bool AccountsProxyModel::acceptSourceItem(const QModelIndex &source) const
{
if (source.isValid()) {
const auto data = sourceModel()->data(source, (int)Role::Account);
if (data.isValid()) {
if (data.canConvert<MyMoneyAccount>()) {
const auto account = data.value<MyMoneyAccount>();
if ((hideClosedAccounts() && account.isClosed()))
return false;
// we hide stock accounts if not in expert mode
if (account.isInvest() && hideEquityAccounts())
return false;
// we hide equity accounts if not in expert mode
- if (account.accountType() == MyMoneyAccount::Equity && hideEquityAccounts())
+ if (account.accountType() == eMyMoney::Account::Equity && hideEquityAccounts())
return false;
// we hide unused income and expense accounts if the specific flag is set
- if ((account.accountType() == MyMoneyAccount::Income || account.accountType() == MyMoneyAccount::Expense) && hideUnusedIncomeExpenseAccounts()) {
+ if ((account.accountType() == eMyMoney::Account::Income || account.accountType() == eMyMoney::Account::Expense) && hideUnusedIncomeExpenseAccounts()) {
const auto totalValue = sourceModel()->data(source, (int)Role::TotalValue);
if (totalValue.isValid() && totalValue.value<MyMoneyMoney>().isZero()) {
emit unusedIncomeExpenseAccountHidden();
return false;
}
}
if (d->m_typeList.contains(account.accountType()))
return true;
} else if (data.canConvert<MyMoneyInstitution>() && sourceModel()->rowCount(source) == 0) {
// if this is an institution that has no children show it only if hide unused institutions (hide closed accounts for now) is not checked
return !hideClosedAccounts();
}
// let the visibility of all other institutions (the ones with children) be controlled by the visibility of their children
}
// all parents that have at least one visible child must be visible
const auto rowCount = sourceModel()->rowCount(source);
for (auto i = 0; i < rowCount; ++i) {
const auto index = sourceModel()->index(i, (int)(int)Column::Account, source);
if (acceptSourceItem(index))
return true;
}
}
return false;
}
/**
* Set if closed accounts should be hidden or not.
* @param hideClosedAccounts
*/
void AccountsProxyModel::setHideClosedAccounts(bool hideClosedAccounts)
{
if (d->m_hideClosedAccounts != hideClosedAccounts) {
d->m_hideClosedAccounts = hideClosedAccounts;
invalidateFilter();
}
}
/**
* Check if closed accounts are hidden or not.
*/
bool AccountsProxyModel::hideClosedAccounts() const
{
return d->m_hideClosedAccounts;
}
/**
* Set if equity and investment accounts should be hidden or not.
* @param hideEquityAccounts
*/
void AccountsProxyModel::setHideEquityAccounts(bool hideEquityAccounts)
{
if (d->m_hideEquityAccounts != hideEquityAccounts) {
d->m_hideEquityAccounts = hideEquityAccounts;
invalidateFilter();
}
}
/**
* Check if equity and investment accounts are hidden or not.
*/
bool AccountsProxyModel::hideEquityAccounts() const
{
return d->m_hideEquityAccounts;
}
/**
* Set if empty categories should be hidden or not.
* @param hideUnusedIncomeExpenseAccounts
*/
void AccountsProxyModel::setHideUnusedIncomeExpenseAccounts(bool hideUnusedIncomeExpenseAccounts)
{
if (d->m_hideUnusedIncomeExpenseAccounts != hideUnusedIncomeExpenseAccounts) {
d->m_hideUnusedIncomeExpenseAccounts = hideUnusedIncomeExpenseAccounts;
invalidateFilter();
}
}
/**
* Check if empty categories are hidden or not.
*/
bool AccountsProxyModel::hideUnusedIncomeExpenseAccounts() const
{
return d->m_hideUnusedIncomeExpenseAccounts;
}
/**
* Returns the number of visible items after filtering. In case @a includeBaseAccounts
* is set to @c true, the 5 base accounts (asset, liability, income, expense and equity)
* will also be counted. The default is @c false.
*/
int AccountsProxyModel::visibleItems(bool includeBaseAccounts) const
{
auto rows = 0;
for (auto i = 0; i < rowCount(QModelIndex()); ++i) {
if(includeBaseAccounts) {
++rows;
}
const auto childIndex = index(i, 0);
if (hasChildren(childIndex)) {
rows += visibleItems(childIndex);
}
}
return rows;
}
/**
* Returns the number of visible items under the given @a index.
* The column of the @a index must be 0, otherwise no count will
* be returned (returns 0).
*/
int AccountsProxyModel::visibleItems(const QModelIndex& index) const
{
auto rows = 0;
if (index.isValid() && index.column() == (int)Column::Account) { // CAUTION! Assumption is being made that Account column number is always 0
const auto *model = index.model();
const auto rowCount = model->rowCount(index);
for (auto i = 0; i < rowCount; ++i) {
++rows;
const auto childIndex = model->index(i, index.column(), index);
if (model->hasChildren(childIndex))
rows += visibleItems(childIndex);
}
}
return rows;
}
void AccountsProxyModel::setSourceColumns(QList<eAccountsModel::Column> *columns)
{
m_mdlColumns = columns;
}
diff --git a/kmymoney/models/accountsproxymodel.h b/kmymoney/models/accountsproxymodel.h
index 95775e561..5b5881d41 100644
--- a/kmymoney/models/accountsproxymodel.h
+++ b/kmymoney/models/accountsproxymodel.h
@@ -1,111 +1,111 @@
/***************************************************************************
* Copyright 2010 Cristian Onet onet.cristian@gmail.com *
* Copyright 2017 Łukasz Wojniłowicz 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) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 of the license. *
* *
* 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 <http://www.gnu.org/licenses/> *
***************************************************************************/
#ifndef ACCOUNTSPROXYMODEL_H
#define ACCOUNTSPROXYMODEL_H
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
#include <KItemModels/KRecursiveFilterProxyModel>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyaccount.h"
#include "mymoneyinstitution.h"
/**
* A proxy model to provide various sorting and filtering operations for @ref AccountsModel.
*
* Here is an example of how to use this class in combination with the @ref AccountsModel.
* (in the example @a widget is a pointer to a model/view widget):
*
* @code
* AccountsFilterProxyModel *filterModel = new AccountsFilterProxyModel(widget);
- * filterModel->addAccountGroup(MyMoneyAccount::Asset);
- * filterModel->addAccountGroup(MyMoneyAccount::Liability);
+ * filterModel->addAccountGroup(eMyMoney::Account::Asset);
+ * filterModel->addAccountGroup(eMyMoney::Account::Liability);
* filterModel->setSourceModel(Models::instance()->accountsModel());
* filterModel->sort(0);
*
* widget->setModel(filterModel);
* @endcode
*
* @see AccountsModel
*
* @author Cristian Onet 2010
*
*/
namespace eAccountsModel {
enum class Column;
}
class AccountsProxyModel : public KRecursiveFilterProxyModel
{
Q_OBJECT
public:
AccountsProxyModel(QObject *parent = nullptr);
~AccountsProxyModel();
- void addAccountType(MyMoneyAccount::accountTypeE type);
- void addAccountGroup(const QVector<MyMoneyAccount::_accountTypeE> &groups);
- void removeAccountType(MyMoneyAccount::accountTypeE type);
+ void addAccountType(eMyMoney::Account type);
+ void addAccountGroup(const QVector<eMyMoney::Account> &groups);
+ void removeAccountType(eMyMoney::Account type);
void clear();
void setHideClosedAccounts(bool hideClosedAccounts);
bool hideClosedAccounts() const;
void setHideEquityAccounts(bool hideEquityAccounts);
bool hideEquityAccounts() const;
void setHideUnusedIncomeExpenseAccounts(bool hideUnusedIncomeExpenseAccounts);
bool hideUnusedIncomeExpenseAccounts() const;
int visibleItems(bool includeBaseAccounts = false) const;
void setSourceColumns(QList<eAccountsModel::Column> *columns);
QList<eAccountsModel::Column> *m_mdlColumns;
protected:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
bool acceptSourceItem(const QModelIndex &source) const;
bool filterAcceptsRowOrChildRows(int source_row, const QModelIndex &source_parent) const;
int visibleItems(const QModelIndex& index) const;
signals:
void unusedIncomeExpenseAccountHidden() const;
private:
class Private;
Private* const d;
};
#endif
diff --git a/kmymoney/models/equitiesmodel.cpp b/kmymoney/models/equitiesmodel.cpp
index ba50b234c..4bedee4f6 100644
--- a/kmymoney/models/equitiesmodel.cpp
+++ b/kmymoney/models/equitiesmodel.cpp
@@ -1,466 +1,471 @@
/***************************************************************************
equitiesmodel.cpp
-------------------
copyright : (C) 2017 by Łukasz Wojniłowicz <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 "equitiesmodel.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QMenu>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
+#include "mymoneyfile.h"
+#include "mymoneyaccount.h"
+#include "mymoneysecurity.h"
+#include "mymoneyprice.h"
+
class EquitiesModel::Private
{
public:
Private() : m_file(MyMoneyFile::instance())
{
QVector<Column> columns {Column::Equity, Column::Symbol, Column::Value,
Column::Quantity, Column::Price};
foreach (auto const column, columns)
m_columns.append(column);
}
~Private() {}
void loadInvestmentAccount(QStandardItem *node, const MyMoneyAccount &invAcc)
{
auto itInvAcc = new QStandardItem(invAcc.name());
node->appendRow(itInvAcc); // investment account is meant to be added under root item
itInvAcc->setEditable(false);
itInvAcc->setColumnCount(m_columns.count());
setAccountData(node, itInvAcc->row(), invAcc, m_columns);
const auto strStkAccList = invAcc.accountList(); // only stock or bond accounts are expected here
foreach (const auto strStkAcc, strStkAccList) {
auto stkAcc = m_file->account(strStkAcc);
auto itStkAcc = new QStandardItem(strStkAcc);
itStkAcc->setEditable(false);
itInvAcc->appendRow(itStkAcc);
setAccountData(itInvAcc, itStkAcc->row(), stkAcc, m_columns);
}
}
void setAccountData(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList<Column> &columns)
{
QStandardItem *cell;
auto getCell = [&, row](const auto column) {
cell = node->child(row, column); // try to get QStandardItem
if (!cell) { // it may be uninitialized
cell = new QStandardItem; // so create one
node->setChild(row, column, cell); // and add it under the node
cell->setEditable(false); // and don't forget that it's non-editable
}
};
auto colNum = m_columns.indexOf(Column::Equity);
if (colNum == -1)
return;
// Equity
getCell(colNum);
if (columns.contains(Column::Equity)) {
cell->setData(account.name(), Qt::DisplayRole);
cell->setData(account.id(), Role::EquityID);
cell->setData(account.currencyId(), Role::SecurityID);
}
- if (account.accountType() == MyMoneyAccount::Investment) // investments accounts are not meant to be displayed, so stop here
+ if (account.accountType() == eMyMoney::Account::Investment) // investments accounts are not meant to be displayed, so stop here
return;
// Symbol
if (columns.contains(Column::Symbol)) {
colNum = m_columns.indexOf(Column::Symbol);
if (colNum != -1) {
auto security = m_file->security(account.currencyId());
getCell(colNum);
cell->setData(security.tradingSymbol(), Qt::DisplayRole);
}
}
setAccountBalanceAndValue(node, row, account, columns);
}
void setAccountBalanceAndValue(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList<Column> &columns)
{
QStandardItem *cell;
auto getCell = [&, row](const auto column) {
cell = node->child(row, column); // try to get QStandardItem
if (!cell) { // it may be uninitialized
cell = new QStandardItem; // so create one
node->setChild(row, column, cell); // and add it under the node
cell->setEditable(false); // and don't forget that it's non-editable
}
};
auto colNum = m_columns.indexOf(Column::Equity);
if (colNum == -1)
return;
auto balance = m_file->balance(account.id());
auto security = m_file->security(account.currencyId());
auto tradingCurrency = m_file->security(security.tradingCurrency());
auto price = m_file->price(account.currencyId(), tradingCurrency.id());
// Value
if (columns.contains(Column::Value)) {
colNum = m_columns.indexOf(Column::Value);
if (colNum != -1) {
getCell(colNum);
if (price.isValid()) {
auto prec = MyMoneyMoney::denomToPrec(tradingCurrency.smallestAccountFraction());
auto value = balance * price.rate(tradingCurrency.id());
auto strValue = QVariant(value.formatMoney(tradingCurrency.tradingSymbol(), prec));
cell->setData(strValue, Qt::DisplayRole);
} else {
cell->setData(QVariant("---"), Qt::DisplayRole);
}
cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
}
}
// Quantity
if (columns.contains(Column::Quantity)) {
colNum = m_columns.indexOf(Column::Quantity);
if (colNum != -1) {
getCell(colNum);
auto prec = MyMoneyMoney::denomToPrec(security.smallestAccountFraction());
auto strQuantity = QVariant(balance.formatMoney(QString(), prec));
cell->setData(strQuantity, Qt::DisplayRole);
}
}
// Price
if (columns.contains(Column::Price)) {
colNum = m_columns.indexOf(Column::Price);
if (colNum != -1) {
getCell(colNum);
if (price.isValid()) {
auto prec = security.pricePrecision();
auto strPrice = QVariant(price.rate(tradingCurrency.id()).formatMoney(tradingCurrency.tradingSymbol(), prec));
cell->setData(strPrice, Qt::DisplayRole);
} else {
cell->setData(QVariant("---"), Qt::DisplayRole);
}
cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
}
}
}
QStandardItem *itemFromId(QStandardItemModel *model, const QString &id, const Role role)
{
const auto itemList = model->match(model->index(0, 0), role, QVariant(id), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
if (!itemList.isEmpty())
return model->itemFromIndex(itemList.first());
return nullptr;
}
MyMoneyFile *m_file;
QList<EquitiesModel::Column> m_columns;
};
EquitiesModel::EquitiesModel(QObject *parent)
: QStandardItemModel(parent), d(new Private)
{
init();
}
EquitiesModel::~EquitiesModel()
{
delete d;
}
void EquitiesModel::init()
{
QStringList headerLabels;
foreach (const auto column, d->m_columns)
headerLabels.append(getHeaderName(column));
setHorizontalHeaderLabels(headerLabels);
}
void EquitiesModel::load()
{
this->blockSignals(true);
auto rootItem = invisibleRootItem();
QList<MyMoneyAccount> accList;
d->m_file->accountList(accList); // get all available accounts
foreach (const auto acc, accList)
- if (acc.accountType() == MyMoneyAccount::Investment) // but add only investment accounts (and its children) to the model
+ if (acc.accountType() == eMyMoney::Account::Investment) // but add only investment accounts (and its children) to the model
d->loadInvestmentAccount(rootItem, acc);
this->blockSignals(false);
}
/**
* Notify the model that an object has been added. An action is performed only if the object is an account.
*
*/
-void EquitiesModel::slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj)
+void EquitiesModel::slotObjectAdded(eMyMoney::File::Object objType, const MyMoneyObject * const obj)
{
// check whether change is about accounts
- if (objType != MyMoneyFile::notifyAccount)
+ if (objType != eMyMoney::File::Object::Account)
return;
// check whether change is about either investment or stock account
const auto acc = dynamic_cast<const MyMoneyAccount * const>(obj);
if (!acc ||
- (acc->accountType() != MyMoneyAccount::Investment &&
- acc->accountType() != MyMoneyAccount::Stock))
+ (acc->accountType() != eMyMoney::Account::Investment &&
+ acc->accountType() != eMyMoney::Account::Stock))
return;
auto itAcc = d->itemFromId(this, acc->id(), Role::EquityID);
QStandardItem *itParentAcc;
- if (acc->accountType() == MyMoneyAccount::Investment) // if it's investment account then its parent is root item
+ if (acc->accountType() == eMyMoney::Account::Investment) // if it's investment account then its parent is root item
itParentAcc = invisibleRootItem();
else // otherwise it's stock account and its parent is investment account
itParentAcc = d->itemFromId(this, acc->parentAccountId(), Role::InvestmentID);
// if account doesn't exist in model then add it
if (!itAcc) {
itAcc = new QStandardItem(acc->name());
itParentAcc->appendRow(itAcc);
itAcc->setEditable(false);
}
d->setAccountData(itParentAcc, itAcc->row(), *acc, d->m_columns);
}
/**
* Notify the model that an object has been modified. An action is performed only if the object is an account.
*
*/
-void EquitiesModel::slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj)
+void EquitiesModel::slotObjectModified(eMyMoney::File::Object objType, const MyMoneyObject * const obj)
{
const MyMoneyAccount *acc;
QStandardItem *itAcc;
switch (objType) {
- case MyMoneyFile::notifyAccount:
+ case eMyMoney::File::Object::Account:
{
auto tmpAcc = dynamic_cast<const MyMoneyAccount * const>(obj);
- if (!tmpAcc || tmpAcc->accountType() != MyMoneyAccount::Stock)
+ if (!tmpAcc || tmpAcc->accountType() != eMyMoney::Account::Stock)
return;
acc = tmpAcc;
itAcc = d->itemFromId(this, acc->id(), Role::EquityID);
break;
}
- case MyMoneyFile::notifySecurity:
+ case eMyMoney::File::Object::Security:
{
auto sec = dynamic_cast<const MyMoneySecurity * const>(obj);
itAcc = d->itemFromId(this, sec->id(), Role::SecurityID);
if (!itAcc)
return;
const auto idAcc = itAcc->data(Role::EquityID).toString();
acc = &d->m_file->account(idAcc);
break;
}
default:
return;
}
auto itParentAcc = d->itemFromId(this, acc->parentAccountId(), Role::InvestmentID);
auto modelID = itParentAcc->data(Role::InvestmentID).toString(); // get parent account from model
if (modelID == acc->parentAccountId()) { // and if it matches with those from file then modify only
d->setAccountData(itParentAcc, itAcc->row(), *acc, d->m_columns);
} else { // and if not then reparent
- slotObjectRemoved(MyMoneyFile::notifyAccount, acc->id());
- slotObjectAdded(MyMoneyFile::notifyAccount, obj);
+ slotObjectRemoved(eMyMoney::File::Object::Account, acc->id());
+ slotObjectAdded(eMyMoney::File::Object::Account, obj);
}
}
/**
* Notify the model that an object has been removed. An action is performed only if the object is an account.
*
*/
-void EquitiesModel::slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id)
+void EquitiesModel::slotObjectRemoved(eMyMoney::File::Object objType, const QString& id)
{
- if (objType != MyMoneyFile::notifyAccount)
+ if (objType != eMyMoney::File::Object::Account)
return;
const auto indexList = match(index(0, 0), Role::EquityID, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive));
foreach (const auto index, indexList)
removeRow(index.row(), index.parent());
}
/**
* Notify the model that the account balance has been changed.
*/
void EquitiesModel::slotBalanceOrValueChanged(const MyMoneyAccount &account)
{
- if (account.accountType() != MyMoneyAccount::Stock)
+ if (account.accountType() != eMyMoney::Account::Stock)
return;
const auto itAcc = d->itemFromId(this, account.id(), Role::EquityID);
if (!itAcc)
return;
d->setAccountBalanceAndValue(itAcc->parent(), itAcc->row(), account, d->m_columns);
}
auto EquitiesModel::getColumns()
{
return &d->m_columns;
}
QString EquitiesModel::getHeaderName(const Column column)
{
switch(column) {
case Equity:
return i18n("Equity");
case Symbol:
return i18n("Symbol");
case Value:
return i18n("Value");
case Quantity:
return i18n("Quantity");
case Price:
return i18n("Price");
default:
return QString();
}
}
class EquitiesFilterProxyModel::Private
{
public:
Private() :
m_mdlColumns(nullptr),
m_file(MyMoneyFile::instance()),
m_hideClosedAccounts(false),
m_hideZeroBalanceAccounts(false)
{}
~Private() {}
QList<EquitiesModel::Column> *m_mdlColumns;
QList<EquitiesModel::Column> m_visColumns;
MyMoneyFile *m_file;
bool m_hideClosedAccounts;
bool m_hideZeroBalanceAccounts;
};
EquitiesFilterProxyModel::EquitiesFilterProxyModel(QObject *parent, EquitiesModel *model, const QList<EquitiesModel::Column> &columns)
: KRecursiveFilterProxyModel(parent), d(new Private)
{
setDynamicSortFilter(true);
setFilterKeyColumn(-1);
setSortLocaleAware(true);
setFilterCaseSensitivity(Qt::CaseInsensitive);
setSourceModel(model);
d->m_mdlColumns = model->getColumns();
d->m_visColumns.append(columns);
}
EquitiesFilterProxyModel::~EquitiesFilterProxyModel()
{
delete d;
}
/**
* Set if closed accounts should be hidden or not.
* @param hideClosedAccounts
*/
void EquitiesFilterProxyModel::setHideClosedAccounts(const bool hideClosedAccounts)
{
d->m_hideClosedAccounts = hideClosedAccounts;
}
/**
* Set if zero balance accounts should be hidden or not.
* @param hideZeroBalanceAccounts
*/
void EquitiesFilterProxyModel::setHideZeroBalanceAccounts(const bool hideZeroBalanceAccounts)
{
d->m_hideZeroBalanceAccounts = hideZeroBalanceAccounts;
}
bool EquitiesFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
if (d->m_visColumns.isEmpty() || d->m_visColumns.contains(d->m_mdlColumns->at(source_column)))
return true;
return false;
}
bool EquitiesFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (d->m_hideClosedAccounts || d->m_hideZeroBalanceAccounts) {
const auto ixRow = sourceModel()->index(source_row, EquitiesModel::Equity, source_parent);
const auto idAcc = sourceModel()->data(ixRow, EquitiesModel::EquityID).toString();
const auto acc = d->m_file->account(idAcc);
if (d->m_hideClosedAccounts &&
acc.isClosed())
return false;
if (d->m_hideZeroBalanceAccounts &&
- acc.accountType() != MyMoneyAccount::Investment && acc.balance().isZero()) // we should never hide investment account because all underlaying stocks will be hidden as well
+ acc.accountType() != eMyMoney::Account::Investment && acc.balance().isZero()) // we should never hide investment account because all underlaying stocks will be hidden as well
return false;
}
return true;
}
QList<EquitiesModel::Column> &EquitiesFilterProxyModel::getVisibleColumns()
{
return d->m_visColumns;
}
void EquitiesFilterProxyModel::slotColumnsMenu(const QPoint)
{
// construct all hideable columns list
const QList<EquitiesModel::Column> idColumns {
EquitiesModel::Symbol, EquitiesModel::Value,
EquitiesModel::Quantity, EquitiesModel::Price
};
// create menu
QMenu menu(i18n("Displayed columns"));
QList<QAction *> actions;
foreach (const auto idColumn, idColumns) {
auto a = new QAction(nullptr);
a->setObjectName(QString::number(idColumn));
a->setText(EquitiesModel::getHeaderName(idColumn));
a->setCheckable(true);
a->setChecked(d->m_visColumns.contains(idColumn));
actions.append(a);
}
menu.addActions(actions);
// execute menu and get result
const auto retAction = menu.exec(QCursor::pos());
if (retAction) {
const auto idColumn = static_cast<EquitiesModel::Column>(retAction->objectName().toInt());
const auto isChecked = retAction->isChecked();
const auto contains = d->m_visColumns.contains(idColumn);
if (isChecked && !contains) { // column has just been enabled
d->m_visColumns.append(idColumn); // change filtering variable
emit columnToggled(idColumn, true); // emit signal for method to add column to model
invalidate(); // refresh model to reflect recent changes
} else if (!isChecked && contains) { // column has just been disabled
d->m_visColumns.removeOne(idColumn);
emit columnToggled(idColumn, false);
invalidate();
}
}
}
diff --git a/kmymoney/models/equitiesmodel.h b/kmymoney/models/equitiesmodel.h
index 6ee6927f1..e0e8be12c 100644
--- a/kmymoney/models/equitiesmodel.h
+++ b/kmymoney/models/equitiesmodel.h
@@ -1,94 +1,96 @@
/***************************************************************************
equitiesmodel.h
-------------------
copyright : (C) 2017 by Łukasz Wojniłowicz <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 EQUITIESMODEL_H
#define EQUITIESMODEL_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QStandardItemModel>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KItemModels/KRecursiveFilterProxyModel>
// ----------------------------------------------------------------------------
// Project Includes
-#include "mymoneyfile.h"
+#include "mymoneyenums.h"
+class MyMoneyObject;
+class MyMoneyAccount;
class EquitiesModel : public QStandardItemModel
{
Q_OBJECT
public:
enum Column { Equity = 0, Symbol, Value, Quantity, Price };
enum Role { InvestmentID = Qt::UserRole, EquityID = Qt::UserRole, SecurityID = Qt::UserRole + 1 };
~EquitiesModel();
auto getColumns();
static QString getHeaderName(const Column column);
public slots:
- void slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id);
+ void slotObjectAdded(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void slotObjectModified(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void slotObjectRemoved(eMyMoney::File::Object objType, const QString& id);
void slotBalanceOrValueChanged(const MyMoneyAccount &account);
private:
EquitiesModel(QObject *parent = nullptr);
EquitiesModel(const EquitiesModel&);
EquitiesModel& operator=(EquitiesModel&);
friend class Models; // only this class can create EquitiesModel
void init();
void load();
protected:
class Private;
Private* const d;
};
class EquitiesFilterProxyModel : public KRecursiveFilterProxyModel
{
Q_OBJECT
public:
EquitiesFilterProxyModel(QObject *parent , EquitiesModel *model, const QList<EquitiesModel::Column> &columns = QList<EquitiesModel::Column>());
~EquitiesFilterProxyModel();
QList<EquitiesModel::Column> &getVisibleColumns();
void setHideClosedAccounts(const bool hideClosedAccounts);
void setHideZeroBalanceAccounts(const bool hideZeroBalanceAccounts);
signals:
void columnToggled(const EquitiesModel::Column column, const bool show);
public slots:
void slotColumnsMenu(const QPoint);
protected:
bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override;
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
private:
class Private;
Private* const d;
};
#endif // EQUITIESMODEL_H
diff --git a/kmymoney/models/ledgermodel.cpp b/kmymoney/models/ledgermodel.cpp
index b8a624ad8..a5a5df388 100644
--- a/kmymoney/models/ledgermodel.cpp
+++ b/kmymoney/models/ledgermodel.cpp
@@ -1,1447 +1,1452 @@
/***************************************************************************
ledgermodel.cpp
-------------------
begin : Sat Aug 8 2015
copyright : (C) 2015 by Thomas Baumgart
email : Thomas Baumgart <tbaumgart@kde.org>
***************************************************************************/
/***************************************************************************
* *
* 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 "ledgermodel.h"
#include "models.h"
#include "costcentermodel.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QDebug>
#include <QString>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneytransaction.h"
+#include "mymoneytransactionfilter.h"
#include "mymoneyfile.h"
+#include "mymoneypayee.h"
#include "mymoneymoney.h"
#include "kmymoneyutils.h"
#include "kmymoneyglobalsettings.h"
+#include "mymoneyenums.h"
+
+using namespace eMyMoney;
LedgerItem::LedgerItem()
{
}
LedgerItem::~LedgerItem()
{
}
LedgerTransaction::LedgerTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s)
: LedgerItem()
, m_transaction(t)
, m_split(s)
, m_erroneous(false)
{
// extract the payee id
QString payeeId = m_split.payeeId();
if(payeeId.isEmpty()) {
QList<MyMoneySplit>::const_iterator it;
for(it = m_transaction.splits().constBegin(); it != m_transaction.splits().constEnd(); ++it) {
if(!(*it).payeeId().isEmpty()) {
payeeId = (*it).payeeId();
break;
}
}
}
if(!payeeId.isEmpty()) {
m_payeeId = payeeId;
m_payeeName = MyMoneyFile::instance()->payee(payeeId).name();
}
m_account = MyMoneyFile::instance()->accountToCategory(m_split.accountId());
m_costCenterId = m_split.costCenterId();
// A transaction can have more than 2 splits ...
if(m_transaction.splitCount() > 2) {
m_counterAccount = i18n("Split transaction");
// ... exactly two splits ...
} else if(m_transaction.splitCount() == 2) {
QList<MyMoneySplit>::const_iterator it;
for(it = m_transaction.splits().constBegin(); it != m_transaction.splits().constEnd(); ++it) {
if((*it).id() != m_split.id()) {
m_counterAccountId = (*it).accountId();
m_counterAccount = MyMoneyFile::instance()->accountToCategory(m_counterAccountId);
// in case the own split does not have a costcenter, but the counter split does
// we use it nevertheless
if(m_costCenterId.isEmpty())
m_costCenterId = (*it).costCenterId();
break;
}
}
// ... or a single split
} else if(!m_split.shares().isZero()) {
m_counterAccount = i18n("*** UNASSIGNED ***");
}
// The transaction is erroneous in case it is not balanced
m_erroneous = !m_transaction.splitSum().isZero();
// now take care of the values
setupValueDisplay();
}
LedgerTransaction::~LedgerTransaction()
{
}
void LedgerTransaction::setupValueDisplay()
{
const MyMoneyFile* file = MyMoneyFile::instance();
const MyMoneyAccount acc = file->account(m_split.accountId());
MyMoneyMoney value = m_split.value(m_transaction.commodity(), acc.currencyId());
m_signedShares = value.formatMoney(acc.fraction());
if(value.isNegative()) {
m_shares = m_payment = (-value).formatMoney(acc.fraction());
} else {
m_shares = m_deposit = m_signedShares;
}
// figure out if it is a debit or credit split. s.a. https://en.wikipedia.org/wiki/Debits_and_credits#Aspects_of_transactions
if(m_split.shares().isNegative()) {
m_sharesSuffix = i18nc("Credit suffix", "Cr.");
} else {
m_sharesSuffix = i18nc("Debit suffix", "Dr.");
}
}
QDate LedgerTransaction::postDate() const
{
return m_transaction.postDate();
}
QString LedgerTransaction::transactionSplitId() const
{
QString rc;
if(!m_transaction.id().isEmpty()) {
rc = QString("%1-%2").arg(m_transaction.id()).arg(m_split.id());
}
return rc;
}
MyMoneySplit::reconcileFlagE LedgerTransaction::reconciliationState() const
{
return m_split.reconcileFlag();
}
QString LedgerTransaction::reconciliationStateShort() const
{
QString rc;
switch(m_split.reconcileFlag()) {
case MyMoneySplit::NotReconciled:
default:
break;
case MyMoneySplit::Cleared:
rc = i18nc("Reconciliation flag C", "C");
break;
case MyMoneySplit::Reconciled:
rc = i18nc("Reconciliation flag R", "R");
break;
case MyMoneySplit::Frozen:
rc = i18nc("Reconciliation flag F", "F");
break;
}
return rc;
}
QString LedgerTransaction::reconciliationStateLong() const
{
QString rc;
switch(m_split.reconcileFlag()) {
case MyMoneySplit::NotReconciled:
default:
rc = i18nc("Reconciliation flag empty", "Not reconciled");
break;
case MyMoneySplit::Cleared:
rc = i18nc("Reconciliation flag C", "Cleared");
break;
case MyMoneySplit::Reconciled:
rc = i18nc("Reconciliation flag R", "Reconciled");
break;
case MyMoneySplit::Frozen:
rc = i18nc("Reconciliation flag F", "Frozen");
break;
}
return rc;
}
void LedgerTransaction::setBalance(QString txt)
{
m_balance = txt;
}
QString LedgerTransaction::memo() const
{
QString memo = m_split.memo();
if(memo.isEmpty()) {
memo = m_transaction.memo();
}
return memo;
}
LedgerTransaction LedgerTransaction::newTransactionEntry()
{
// create a dummy entry for new transactions
MyMoneyTransaction t;
t.setPostDate(QDate(2900,12,31));
return LedgerTransaction(t, MyMoneySplit());
}
bool LedgerTransaction::isNewTransactionEntry() const
{
return m_transaction.id().isEmpty() && m_split.id().isEmpty();
}
QString LedgerTransaction::transactionCommodity() const
{
return m_transaction.commodity();
}
QString LedgerSchedule::transactionSplitId() const
{
return QString("%1-%2").arg(m_schedule.id()).arg(m_split.id());
}
LedgerSchedule::LedgerSchedule(const MyMoneySchedule& s, const MyMoneyTransaction& t, const MyMoneySplit& sp)
: LedgerTransaction(t, sp)
, m_schedule(s)
{
}
LedgerSchedule::~LedgerSchedule()
{
}
QString LedgerSchedule::scheduleId() const
{
return m_schedule.id();
}
LedgerSplit::LedgerSplit(const MyMoneyTransaction& t, const MyMoneySplit& s)
: LedgerTransaction(t, s)
{
// override the settings made in the base class
m_payeeName.clear();
m_payeeId = m_split.payeeId();
if(!m_payeeId.isEmpty()) {
try {
m_payeeName = MyMoneyFile::instance()->payee(m_payeeId).name();
} catch(MyMoneyException&) {
qDebug() << "payee" << m_payeeId << "not found.";
}
}
}
LedgerSplit::~LedgerSplit()
{
}
QString LedgerSplit::memo() const
{
return m_split.memo();
}
LedgerSortFilterProxyModel::LedgerSortFilterProxyModel(QObject* parent)
: QSortFilterProxyModel(parent)
, m_showNewTransaction(false)
- , m_accountType(MyMoneyAccount::Asset)
+ , m_accountType(Account::Asset)
{
setFilterRole(LedgerRole::AccountIdRole);
setFilterKeyColumn(0);
setSortRole(LedgerRole::PostDateRole);
setDynamicSortFilter(true);
}
LedgerSortFilterProxyModel::~LedgerSortFilterProxyModel()
{
}
-void LedgerSortFilterProxyModel::setAccountType(MyMoneyAccount::accountTypeE type)
+void LedgerSortFilterProxyModel::setAccountType(Account type)
{
m_accountType = type;
}
QVariant LedgerSortFilterProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch(section) {
case LedgerModel::PaymentColumn:
switch(m_accountType) {
- case MyMoneyAccount::CreditCard:
+ case Account::CreditCard:
return i18nc("Payment made with credit card", "Charge");
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::AssetLoan:
+ case Account::Asset:
+ case Account::AssetLoan:
return i18nc("Decrease of asset/liability value", "Decrease");
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Loan:
+ case Account::Liability:
+ case Account::Loan:
return i18nc("Increase of asset/liability value", "Increase");
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
+ case Account::Income:
+ case Account::Expense:
return i18n("Income");
default:
break;
}
break;
case LedgerModel::DepositColumn:
switch(m_accountType) {
- case MyMoneyAccount::CreditCard:
+ case Account::CreditCard:
return i18nc("Payment towards credit card", "Payment");
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::AssetLoan:
+ case Account::Asset:
+ case Account::AssetLoan:
return i18nc("Increase of asset/liability value", "Increase");
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Loan:
+ case Account::Liability:
+ case Account::Loan:
return i18nc("Decrease of asset/liability value", "Decrease");
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
+ case Account::Income:
+ case Account::Expense:
return i18n("Expense");
default:
break;
}
break;
}
}
return QSortFilterProxyModel::headerData(section, orientation, role);
}
bool LedgerSortFilterProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
{
// make sure that the dummy transaction is shown last in any case
if(left.data(LedgerRole::TransactionSplitIdRole).toString().isEmpty()) {
return false;
} else if(right.data(LedgerRole::TransactionSplitIdRole).toString().isEmpty()) {
return true;
}
// make sure schedules are shown past real transactions
if(!left.data(LedgerRole::ScheduleIdRole).toString().isEmpty()
&& right.data(LedgerRole::ScheduleIdRole).toString().isEmpty()) {
// left is schedule, right is not
return false;
} else if(left.data(LedgerRole::ScheduleIdRole).toString().isEmpty()
&& !right.data(LedgerRole::ScheduleIdRole).toString().isEmpty()) {
// right is schedule, left is not
return true;
}
// otherwise use normal sorting
return QSortFilterProxyModel::lessThan(left, right);
}
bool LedgerSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
{
if(m_showNewTransaction) {
QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
if(idx.data(LedgerRole::TransactionSplitIdRole).toString().isEmpty()) {
return true;
}
}
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}
bool LedgerSortFilterProxyModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
QModelIndex sourceIndex = mapToSource(index);
return sourceModel()->setData(sourceIndex, value, role);
}
void LedgerSortFilterProxyModel::setShowNewTransaction(bool show)
{
const bool changed = show != m_showNewTransaction;
m_showNewTransaction = show;
if(changed) {
invalidate();
}
}
struct LedgerModel::Private
{
Private() {}
~Private() {
for(int i = 0; i < m_ledgerItems.count(); ++i) {
delete m_ledgerItems.at(i);
}
}
MyMoneyTransaction m_lastTransactionStored;
QVector<LedgerItem*> m_ledgerItems;
};
LedgerModel::LedgerModel(QObject* parent)
: QAbstractTableModel(parent)
, d(new Private)
{
MyMoneyFile* file = MyMoneyFile::instance();
- connect(file, SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), this, SLOT(addTransaction(MyMoneyFile::notificationObjectT,MyMoneyObject*const)));
- connect(file, SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), this, SLOT(modifyTransaction(MyMoneyFile::notificationObjectT,MyMoneyObject*const)));
- connect(file, SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), this, SLOT(removeTransaction(MyMoneyFile::notificationObjectT,QString)));
+ connect(file, SIGNAL(objectAdded(File::Object,MyMoneyObject*const)), this, SLOT(addTransaction(File::Object,MyMoneyObject*const)));
+ connect(file, SIGNAL(objectModified(File::Object,MyMoneyObject*const)), this, SLOT(modifyTransaction(File::Object,MyMoneyObject*const)));
+ connect(file, SIGNAL(objectRemoved(File::Object,QString)), this, SLOT(removeTransaction(File::Object,QString)));
- connect(file, SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), this, SLOT(addSchedule(MyMoneyFile::notificationObjectT,MyMoneyObject*const)));
- connect(file, SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), this, SLOT(modifySchedule(MyMoneyFile::notificationObjectT,MyMoneyObject*const)));
- connect(file, SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), this, SLOT(removeSchedule(MyMoneyFile::notificationObjectT,QString)));
+ connect(file, SIGNAL(objectAdded(File::Object,MyMoneyObject*const)), this, SLOT(addSchedule(File::Object,MyMoneyObject*const)));
+ connect(file, SIGNAL(objectModified(File::Object,MyMoneyObject*const)), this, SLOT(modifySchedule(File::Object,MyMoneyObject*const)));
+ connect(file, SIGNAL(objectRemoved(File::Object,QString)), this, SLOT(removeSchedule(File::Object,QString)));
}
LedgerModel::~LedgerModel()
{
}
int LedgerModel::rowCount(const QModelIndex& parent) const
{
// since the ledger model is a simple table model, we only
// return the rowCount for the hiddenRootItem. and zero otherwise
if(parent.isValid()) {
return 0;
}
return d->m_ledgerItems.count();
}
int LedgerModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return NumberOfLedgerColumns;
}
Qt::ItemFlags LedgerModel::flags(const QModelIndex& index) const
{
Qt::ItemFlags flags;
if(!index.isValid())
return flags;
if(index.row() < 0 || index.row() >= d->m_ledgerItems.count())
return flags;
return d->m_ledgerItems[index.row()]->flags();
}
QVariant LedgerModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch(section) {
case NumberColumn:
return i18nc("Cheque Number", "No.");
case DateColumn:
return i18n("Date");
break;
case SecurityColumn:
return i18n("Security");
break;
case CostCenterColumn:
return i18n("CC");
break;
case DetailColumn:
return i18n("Detail");
break;
case ReconciliationColumn:
return i18n("C");
break;
case PaymentColumn:
return i18nc("Payment made from account", "Payment");
break;
case DepositColumn:
return i18nc("Deposit into account", "Deposit");
break;
case QuantityColumn:
return i18n("Quantity");
break;
case PriceColumn:
return i18n("Price");
break;
case AmountColumn:
return i18n("Amount");
break;
case ValueColumn:
return i18n("Value");
break;
case BalanceColumn:
return i18n("Balance");
break;
}
}
else if(orientation == Qt::Vertical && role == Qt::SizeHintRole) {
// as small as possible, so that the delegate has a chance
// to override the information
return QSize(10, 10);
}
return QAbstractItemModel::headerData(section, orientation, role);
}
QVariant LedgerModel::data(const QModelIndex& index, int role) const
{
if(!index.isValid())
return QVariant();
if(index.row() < 0 || index.row() >= d->m_ledgerItems.count())
return QVariant();
QVariant rc;
switch(role) {
case Qt::DisplayRole:
// make sure to never return any displayable text for the dummy entry
if(!d->m_ledgerItems[index.row()]->transactionSplitId().isEmpty()) {
switch(index.column()) {
case NumberColumn:
rc = d->m_ledgerItems[index.row()]->transactionNumber();
break;
case DateColumn:
rc = QLocale().toString(d->m_ledgerItems[index.row()]->postDate(), QLocale::ShortFormat);
break;
case DetailColumn:
rc = d->m_ledgerItems[index.row()]->counterAccount();
break;
case ReconciliationColumn:
rc = d->m_ledgerItems[index.row()]->reconciliationStateShort();
break;
case PaymentColumn:
rc = d->m_ledgerItems[index.row()]->payment();
break;
case DepositColumn:
rc = d->m_ledgerItems[index.row()]->deposit();
break;
case AmountColumn:
rc = d->m_ledgerItems[index.row()]->signedSharesAmount();
break;
case BalanceColumn:
rc = d->m_ledgerItems[index.row()]->balance();
break;
}
}
break;
case Qt::TextAlignmentRole:
switch(index.column()) {
case PaymentColumn:
case DepositColumn:
case AmountColumn:
case BalanceColumn:
case ValueColumn:
rc = QVariant(Qt::AlignRight| Qt::AlignTop);
break;
case ReconciliationColumn:
rc = QVariant(Qt::AlignHCenter | Qt::AlignTop);
break;
default:
rc = QVariant(Qt::AlignLeft | Qt::AlignTop);
break;
}
break;
case Qt::BackgroundColorRole:
if(d->m_ledgerItems[index.row()]->isImported()) {
return KMyMoneyGlobalSettings::schemeColor(SchemeColor::TransactionImported);
}
break;
case LedgerRole::CounterAccountRole:
rc = d->m_ledgerItems[index.row()]->counterAccount();
break;
case LedgerRole::SplitCountRole:
rc = d->m_ledgerItems[index.row()]->splitCount();
break;
case LedgerRole::CostCenterIdRole:
rc = d->m_ledgerItems[index.row()]->costCenterId();
break;
case LedgerRole::PostDateRole:
rc = d->m_ledgerItems[index.row()]->postDate();
break;
case LedgerRole::PayeeNameRole:
rc = d->m_ledgerItems[index.row()]->payeeName();
break;
case LedgerRole::PayeeIdRole:
rc = d->m_ledgerItems[index.row()]->payeeId();
break;
case LedgerRole::AccountIdRole:
rc = d->m_ledgerItems[index.row()]->accountId();
break;
case Qt::EditRole:
case LedgerRole::TransactionSplitIdRole:
rc = d->m_ledgerItems[index.row()]->transactionSplitId();
break;
case LedgerRole::TransactionIdRole:
rc = d->m_ledgerItems[index.row()]->transactionId();
break;
case LedgerRole::ReconciliationRole:
rc = d->m_ledgerItems[index.row()]->reconciliationState();
break;
case LedgerRole::ReconciliationRoleShort:
rc = d->m_ledgerItems[index.row()]->reconciliationStateShort();
break;
case LedgerRole::ReconciliationRoleLong:
rc = d->m_ledgerItems[index.row()]->reconciliationStateLong();
break;
case LedgerRole::SplitValueRole:
rc.setValue(d->m_ledgerItems[index.row()]->value());
break;
case LedgerRole::SplitSharesRole:
rc.setValue(d->m_ledgerItems[index.row()]->shares());
break;
case LedgerRole::ShareAmountRole:
rc.setValue(d->m_ledgerItems[index.row()]->sharesAmount());
break;
case LedgerRole::ShareAmountSuffixRole:
rc.setValue(d->m_ledgerItems[index.row()]->sharesSuffix());
break;
case LedgerRole::ScheduleIdRole:
{
LedgerSchedule* schedule = 0;
schedule = dynamic_cast<LedgerSchedule*>(d->m_ledgerItems[index.row()]);
if(schedule) {
rc = schedule->scheduleId();
}
break;
}
case LedgerRole::MemoRole:
case LedgerRole::SingleLineMemoRole:
rc.setValue(d->m_ledgerItems[index.row()]->memo());
if(role == LedgerRole::SingleLineMemoRole) {
QString txt = rc.toString();
// remove empty lines
txt.replace("\n\n", "\n");
// replace '\n' with ", "
txt.replace('\n', ", ");
rc.setValue(txt);
}
break;
case LedgerRole::NumberRole:
rc = d->m_ledgerItems[index.row()]->transactionNumber();
break;
case LedgerRole::ErroneousRole:
rc = d->m_ledgerItems[index.row()]->isErroneous();
break;
case LedgerRole::ImportRole:
rc = d->m_ledgerItems[index.row()]->isImported();
break;
case LedgerRole::CounterAccountIdRole:
rc = d->m_ledgerItems[index.row()]->counterAccountId();
break;
case LedgerRole::TransactionCommodityRole:
rc = d->m_ledgerItems[index.row()]->transactionCommodity();
break;
case LedgerRole::TransactionRole:
rc.setValue(d->m_ledgerItems[index.row()]->transaction());
break;
case LedgerRole::SplitRole:
rc.setValue(d->m_ledgerItems[index.row()]->split());
break;
}
return rc;
}
bool LedgerModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if(!index.isValid()) {
return false;
}
if(role == Qt::DisplayRole && index.column() == BalanceColumn) {
d->m_ledgerItems[index.row()]->setBalance(value.toString());
return true;
}
qDebug() << "setData(" << index.row() << index.column() << ")" << value << role;
return QAbstractItemModel::setData(index, value, role);
}
void LedgerModel::unload()
{
if(rowCount() > 0) {
beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
for(int i = 0; i < rowCount(); ++i) {
delete d->m_ledgerItems[i];
}
d->m_ledgerItems.clear();
endRemoveRows();
}
}
void LedgerModel::addTransactions(const QList< QPair<MyMoneyTransaction, MyMoneySplit> >& list)
{
if(list.count() > 0) {
beginInsertRows(QModelIndex(), rowCount(), rowCount() + list.count() - 1);
QList< QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
for(it = list.constBegin(); it != list.constEnd(); ++it) {
d->m_ledgerItems.append(new LedgerTransaction((*it).first, (*it).second));
}
endInsertRows();
}
}
void LedgerModel::addTransaction(const LedgerTransaction& t)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
d->m_ledgerItems.append(new LedgerTransaction(t.transaction(), t.split()));
endInsertRows();
}
void LedgerModel::addTransaction(const QString& transactionSplitId)
{
QRegExp transactionSplitIdExp("^(\\w+)-(\\w+)$");
if(transactionSplitIdExp.exactMatch(transactionSplitId)) {
const QString transactionId = transactionSplitIdExp.cap(1);
const QString splitId = transactionSplitIdExp.cap(2);
if(transactionId != d->m_lastTransactionStored.id()) {
try {
d->m_lastTransactionStored = MyMoneyFile::instance()->transaction(transactionId);
} catch(MyMoneyException& e) {
d->m_lastTransactionStored = MyMoneyTransaction();
}
}
try {
MyMoneySplit split = d->m_lastTransactionStored.splitById(splitId);
beginInsertRows(QModelIndex(), rowCount(), rowCount());
d->m_ledgerItems.append(new LedgerTransaction(d->m_lastTransactionStored, split));
endInsertRows();
} catch(MyMoneyException& e) {
d->m_lastTransactionStored = MyMoneyTransaction();
}
}
}
void LedgerModel::addSchedules(const QList<MyMoneySchedule> & list, int previewPeriod)
{
if(list.count() > 0) {
QVector<LedgerItem*> newList;
// create dummy entries for the scheduled transactions if sorted by postdate
// show scheduled transactions which have a scheduled postdate
// within the next 'previewPeriod' days. In reconciliation mode, the
// previewPeriod starts on the statement date.
QDate endDate = QDate::currentDate().addDays(previewPeriod);
#if 0
if (isReconciliationAccount())
endDate = reconciliationDate.addDays(previewPeriod);
#endif
QList<MyMoneySchedule>::const_iterator it;
for(it = list.constBegin(); it != list.constEnd(); ++it) {
MyMoneySchedule schedule = *it;
// now create entries for this schedule until the endDate is reached
for (;;) {
if (schedule.isFinished() || schedule.adjustedNextDueDate() > endDate) {
break;
}
MyMoneyTransaction t(schedule.id(), KMyMoneyUtils::scheduledTransaction(schedule));
// if the transaction is scheduled and overdue, it can't
// certainly be posted in the past. So we take today's date
// as the alternative
if (schedule.isOverdue()) {
t.setPostDate(schedule.adjustedDate(QDate::currentDate(), schedule.weekendOption()));
} else {
t.setPostDate(schedule.adjustedNextDueDate());
}
const QList<MyMoneySplit>& splits = t.splits();
QList<MyMoneySplit>::const_iterator it_s;
// create a model entry for each split of the schedule
for(it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) {
newList.append(new LedgerSchedule(schedule, t, (*it_s)));
}
// keep track of this payment locally (not in the engine)
if (schedule.isOverdue()) {
schedule.setLastPayment(QDate::currentDate());
} else {
schedule.setLastPayment(schedule.nextDueDate());
}
// if this is a one time schedule, we can bail out here as we're done
- if (schedule.occurrence() == MyMoneySchedule::OCCUR_ONCE)
+ if (schedule.occurrence() == Schedule::Occurrence::Once)
break;
// for all others, we check if the next payment date is still 'in range'
QDate nextDueDate = schedule.nextPayment(schedule.nextDueDate());
if (nextDueDate.isValid()) {
schedule.setNextDueDate(nextDueDate);
} else {
break;
}
}
}
beginInsertRows(QModelIndex(), rowCount(), rowCount() + newList.count() - 1);
d->m_ledgerItems += newList;
endInsertRows();
}
}
void LedgerModel::load()
{
qDebug() << "Start loading splits";
// load all transactions and splits into the model
QList<QPair<MyMoneyTransaction, MyMoneySplit> > tList;
MyMoneyTransactionFilter filter;
MyMoneyFile::instance()->transactionList(tList, filter);
addTransactions(tList);
qDebug() << "Loaded" << rowCount() << "elements";
// load all scheduled transactoins and splits into the model
const int splitCount = rowCount();
QList<MyMoneySchedule> sList = MyMoneyFile::instance()->scheduleList();
addSchedules(sList, KMyMoneyGlobalSettings::schedulePreview());
qDebug() << "Loaded" << rowCount()-splitCount << "elements";
// create a dummy entry for new transactions
addTransaction(LedgerTransaction::newTransactionEntry());
qDebug() << "Loaded" << rowCount() << "elements";
}
-void LedgerModel::addTransaction(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj)
+void LedgerModel::addTransaction(File::Object objType, const MyMoneyObject * const obj)
{
- if(objType != MyMoneyFile::notifyTransaction) {
+ if(objType != File::Object::Transaction) {
return;
}
qDebug() << "Adding transaction" << obj->id();
const MyMoneyTransaction * const t = static_cast<const MyMoneyTransaction * const>(obj);
beginInsertRows(QModelIndex(), rowCount(), rowCount() + t->splitCount() - 1);
foreach(MyMoneySplit s, t->splits()) {
d->m_ledgerItems.append(new LedgerTransaction(*t, s));
}
endInsertRows();
// just make sure we're in sync
Q_ASSERT(d->m_ledgerItems.count() == rowCount());
}
-void LedgerModel::modifyTransaction(MyMoneyFile::notificationObjectT objType, const MyMoneyObject* const obj)
+void LedgerModel::modifyTransaction(File::Object objType, const MyMoneyObject* const obj)
{
- if(objType != MyMoneyFile::notifyTransaction) {
+ if(objType != File::Object::Transaction) {
return;
}
const MyMoneyTransaction * const t = static_cast<const MyMoneyTransaction * const>(obj);
// get indexes of all existing splits for this transaction
QModelIndexList list = match(index(0, 0), LedgerRole::TransactionIdRole, obj->id(), -1);
// get list of splits to be stored
QList<MyMoneySplit> splits = t->splits();
int lastRowUsed = -1;
int firstRowUsed = 99999999;
if(list.count()) {
firstRowUsed = list.first().row();
lastRowUsed = list.last().row();
}
qDebug() << "first:" << firstRowUsed << "last:" << lastRowUsed;
while(!list.isEmpty() && !splits.isEmpty()) {
QModelIndex index = list.takeFirst();
MyMoneySplit split = splits.takeFirst();
// get rid of the old split and store new split
qDebug() << "Modify split in row:" << index.row() << t->id() << split.id();
delete d->m_ledgerItems[index.row()];
d->m_ledgerItems[index.row()] = new LedgerTransaction(*t, split);
}
// inform every one else about the changes
if(lastRowUsed != -1) {
qDebug() << "emit dataChanged from" << firstRowUsed << "to" << lastRowUsed;
emit dataChanged(index(firstRowUsed, 0), index(lastRowUsed, columnCount()-1));
} else {
lastRowUsed = rowCount();
}
// now check if we need to add more splits ...
if(!splits.isEmpty() && list.isEmpty()) {
beginInsertRows(QModelIndex(), lastRowUsed, lastRowUsed + splits.count() - 1);
d->m_ledgerItems.insert(lastRowUsed, splits.count(), 0);
while(!splits.isEmpty()) {
MyMoneySplit split = splits.takeFirst();
d->m_ledgerItems[lastRowUsed] = new LedgerTransaction(*t, split);
lastRowUsed++;
}
endInsertRows();
}
// ... or remove some leftovers
if(splits.isEmpty() && !list.isEmpty()) {
firstRowUsed = lastRowUsed - list.count() + 1;
beginRemoveRows(QModelIndex(), firstRowUsed, lastRowUsed);
int count = 0;
while(!list.isEmpty()) {
++count;
QModelIndex index = list.takeFirst();
// get rid of the old split and store new split
qDebug() << "Delete split in row:" << index.row() << data(index, LedgerRole::TransactionSplitIdRole).toString();
delete d->m_ledgerItems[index.row()];
}
d->m_ledgerItems.remove(firstRowUsed, count);
endRemoveRows();
}
// just make sure we're in sync
Q_ASSERT(d->m_ledgerItems.count() == rowCount());
}
-void LedgerModel::removeTransaction(MyMoneyFile::notificationObjectT objType, const QString& id)
+void LedgerModel::removeTransaction(File::Object objType, const QString& id)
{
- if(objType != MyMoneyFile::notifyTransaction) {
+ if(objType != File::Object::Transaction) {
return;
}
QModelIndexList list = match(index(0, 0), LedgerRole::TransactionIdRole, id, -1);
if(list.count()) {
const int firstRowUsed = list[0].row();
beginRemoveRows(QModelIndex(), firstRowUsed, firstRowUsed + list.count() - 1);
for(int row = firstRowUsed; row < firstRowUsed + list.count(); ++row) {
delete d->m_ledgerItems[row];
}
d->m_ledgerItems.remove(firstRowUsed, list.count());
endRemoveRows();
// just make sure we're in sync
Q_ASSERT(d->m_ledgerItems.count() == rowCount());
}
}
-void LedgerModel::addSchedule(MyMoneyFile::notificationObjectT objType, const MyMoneyObject*const obj)
+void LedgerModel::addSchedule(File::Object objType, const MyMoneyObject*const obj)
{
Q_UNUSED(obj);
- if(objType != MyMoneyFile::notifySchedule) {
+ if(objType != File::Object::Schedule) {
return;
}
/// @todo implement LedgerModel::addSchedule
}
-void LedgerModel::modifySchedule(MyMoneyFile::notificationObjectT objType, const MyMoneyObject*const obj)
+void LedgerModel::modifySchedule(File::Object objType, const MyMoneyObject*const obj)
{
Q_UNUSED(obj);
- if(objType != MyMoneyFile::notifySchedule) {
+ if(objType != File::Object::Schedule) {
return;
}
/// @todo implement LedgerModel::modifySchedule
}
-void LedgerModel::removeSchedule(MyMoneyFile::notificationObjectT objType, const QString& id)
+void LedgerModel::removeSchedule(File::Object objType, const QString& id)
{
Q_UNUSED(id);
- if(objType != MyMoneyFile::notifySchedule) {
+ if(objType != File::Object::Schedule) {
return;
}
/// @todo implement LedgerModel::removeSchedule
}
QString LedgerModel::transactionIdFromTransactionSplitId(const QString& transactionSplitId) const
{
QRegExp transactionSplitIdExp("^(\\w+)-\\w+$");
if(transactionSplitIdExp.exactMatch(transactionSplitId)) {
return transactionSplitIdExp.cap(1);
}
return QString();
}
struct SplitModel::Private
{
Private()
: m_invertValues(false)
{}
bool isCreateSplitEntry(const QString& id) const {
return id.isEmpty();
}
MyMoneyTransaction m_transaction;
QVector<MyMoneySplit> m_splits;
bool m_invertValues;
};
SplitModel::SplitModel(QObject* parent)
: QAbstractTableModel(parent)
, d(new Private)
{
}
SplitModel::~SplitModel()
{
}
QString SplitModel::newSplitId()
{
return QLatin1String("New-ID");
}
bool SplitModel::isNewSplitId(const QString& id)
{
return id.compare(newSplitId()) == 0;
}
int SplitModel::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return d->m_splits.count();
}
int SplitModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return NumberOfLedgerColumns;
}
void SplitModel::deepCopy(const SplitModel& right, bool revertSplitSign)
{
beginInsertRows(QModelIndex(), 0, right.rowCount());
d->m_splits = right.d->m_splits;
d->m_transaction = right.d->m_transaction;
if(revertSplitSign) {
for(int idx = 0; idx < d->m_splits.count(); ++idx) {
MyMoneySplit& split = d->m_splits[idx];
split.setShares(-split.shares());
split.setValue(-split.value());
}
}
endInsertRows();
}
QVariant SplitModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch(section) {
case CostCenterColumn:
return i18n("Cost Center");
break;
case DetailColumn:
return i18n("Category");
break;
case NumberColumn:
return i18n("No");
break;
case DateColumn:
return i18n("Date");
break;
case SecurityColumn:
return i18n("Security");
break;
case ReconciliationColumn:
return i18n("C");
break;
case PaymentColumn:
return i18n("Payment");
break;
case DepositColumn:
return i18n("Deposit");
break;
case QuantityColumn:
return i18n("Quantity");
break;
case PriceColumn:
return i18n("Price");
break;
case AmountColumn:
return i18n("Amount");
break;
case ValueColumn:
return i18n("Value");
break;
case BalanceColumn:
return i18n("Balance");
break;
}
}
return QAbstractItemModel::headerData(section, orientation, role);
}
QVariant SplitModel::data(const QModelIndex& index, int role) const
{
if(!index.isValid())
return QVariant();
if(index.row() < 0 || index.row() >= d->m_splits.count())
return QVariant();
QVariant rc;
MyMoneyAccount acc;
MyMoneyMoney value;
const MyMoneySplit& split = d->m_splits[index.row()];
QModelIndex subIndex;
CostCenterModel* ccModel = Models::instance()->costCenterModel();
switch(role) {
case Qt::DisplayRole:
// make sure to never return any displayable text for the dummy entry
if(!d->isCreateSplitEntry(split.id())) {
switch(index.column()) {
case DetailColumn:
rc = MyMoneyFile::instance()->accountToCategory(split.accountId());
break;
case CostCenterColumn:
subIndex = Models::indexById(ccModel, CostCenterModel::CostCenterIdRole, split.costCenterId());
rc = ccModel->data(subIndex);
break;
case NumberColumn:
rc = split.number();
break;
case ReconciliationColumn:
rc = KMyMoneyUtils::reconcileStateToString(split.reconcileFlag(), false);
break;
case PaymentColumn:
if(split.value().isNegative()) {
acc = MyMoneyFile::instance()->account(split.accountId());
rc = (-split).value(d->m_transaction.commodity(), acc.currencyId()).formatMoney(acc.fraction());
}
break;
case DepositColumn:
if(!split.value().isNegative()) {
acc = MyMoneyFile::instance()->account(split.accountId());
rc = split.value(d->m_transaction.commodity(), acc.currencyId()).formatMoney(acc.fraction());
}
break;
default:
break;
}
}
break;
case Qt::TextAlignmentRole:
switch(index.column()) {
case PaymentColumn:
case DepositColumn:
case AmountColumn:
case BalanceColumn:
case ValueColumn:
rc = QVariant(Qt::AlignRight| Qt::AlignTop);
break;
case ReconciliationColumn:
rc = QVariant(Qt::AlignHCenter | Qt::AlignTop);
break;
default:
rc = QVariant(Qt::AlignLeft | Qt::AlignTop);
break;
}
break;
case LedgerRole::AccountIdRole:
rc = split.accountId();
break;
case LedgerRole::AccountRole:
rc = MyMoneyFile::instance()->accountToCategory(split.accountId());
break;
case LedgerRole::TransactionIdRole:
rc = QString("%1").arg(d->m_transaction.id());
break;
case LedgerRole::TransactionSplitIdRole:
rc = QString("%1-%2").arg(d->m_transaction.id()).arg(split.id());
break;
case LedgerRole::SplitIdRole:
rc = split.id();
break;
case LedgerRole::MemoRole:
case LedgerRole::SingleLineMemoRole:
rc = split.memo();
if(role == LedgerRole::SingleLineMemoRole) {
QString txt = rc.toString();
// remove empty lines
txt.replace("\n\n", "\n");
// replace '\n' with ", "
txt.replace('\n', ", ");
rc = txt;
}
break;
case LedgerRole::SplitSharesRole:
rc = QVariant::fromValue<MyMoneyMoney>(split.shares());
break;
case LedgerRole::SplitValueRole:
acc = MyMoneyFile::instance()->account(split.accountId());
rc = QVariant::fromValue<MyMoneyMoney>(split.value(d->m_transaction.commodity(), acc.currencyId()));
break;
case LedgerRole::PayeeNameRole:
try {
rc = MyMoneyFile::instance()->payee(split.payeeId()).name();
} catch(MyMoneyException&e) {
}
break;
case LedgerRole::CostCenterIdRole:
rc = split.costCenterId();
break;
case LedgerRole::TransactionCommodityRole:
return d->m_transaction.commodity();
break;
case LedgerRole::NumberRole:
rc = split.number();
break;
case LedgerRole::PayeeIdRole:
rc = split.payeeId();
break;
default:
if(role >= Qt::UserRole) {
qWarning() << "Undefined role" << role << "(" << role-Qt::UserRole << ") in SplitModel::data";
}
break;
}
return rc;
}
bool SplitModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
bool rc = false;
if(index.isValid()) {
MyMoneySplit& split = d->m_splits[index.row()];
if(split.id().isEmpty()) {
split = MyMoneySplit(newSplitId(), split);
}
QString val;
rc = true;
switch(role) {
case LedgerRole::PayeeIdRole:
split.setPayeeId(value.toString());
break;
case LedgerRole::AccountIdRole:
split.setAccountId(value.toString());
break;
case LedgerRole::MemoRole:
split.setMemo(value.toString());
break;
case LedgerRole::CostCenterIdRole:
val = value.toString();
split.setCostCenterId(value.toString());
break;
case LedgerRole::NumberRole:
split.setNumber(value.toString());
break;
case LedgerRole::SplitSharesRole:
split.setShares(value.value<MyMoneyMoney>());
break;
case LedgerRole::SplitValueRole:
split.setValue(value.value<MyMoneyMoney>());
break;
case LedgerRole::EmitDataChangedRole:
{
// the whole row changed
QModelIndex topLeft = this->index(index.row(), 0);
QModelIndex bottomRight = this->index(index.row(), this->columnCount()-1);
emit dataChanged(topLeft, bottomRight);
}
break;
default:
rc = false;
break;
}
}
return rc;
}
void SplitModel::addSplit(const QString& transactionSplitId)
{
QRegExp transactionSplitIdExp("^(\\w+)-(\\w+)$");
if(transactionSplitIdExp.exactMatch(transactionSplitId)) {
const QString transactionId = transactionSplitIdExp.cap(1);
const QString splitId = transactionSplitIdExp.cap(2);
if(transactionId != d->m_transaction.id()) {
try {
d->m_transaction = MyMoneyFile::instance()->transaction(transactionId);
} catch(MyMoneyException& e) {
d->m_transaction = MyMoneyTransaction();
}
}
try {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
d->m_splits.append(d->m_transaction.splitById(splitId));
endInsertRows();
} catch(MyMoneyException& e) {
d->m_transaction = MyMoneyTransaction();
}
}
}
void SplitModel::addEmptySplitEntry()
{
QModelIndexList list = match(index(0, 0), LedgerRole::SplitIdRole, QString(), -1, Qt::MatchExactly);
if(list.count() == 0) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
// d->m_splits.append(MyMoneySplit(d->newSplitEntryId(), MyMoneySplit()));
d->m_splits.append(MyMoneySplit());
endInsertRows();
}
}
void SplitModel::removeEmptySplitEntry()
{
// QModelIndexList list = match(index(0, 0), SplitIdRole, d->newSplitEntryId(), -1, Qt::MatchExactly);
QModelIndexList list = match(index(0, 0), LedgerRole::SplitIdRole, QString(), -1, Qt::MatchExactly);
if(list.count()) {
QModelIndex index = list.at(0);
beginRemoveRows(QModelIndex(), index.row(), index.row());
d->m_splits.remove(index.row(), 1);
endRemoveRows();
}
}
bool SplitModel::removeRows(int row, int count, const QModelIndex& parent)
{
bool rc = false;
if(count > 0) {
beginRemoveRows(parent, row, row + count - 1);
d->m_splits.remove(row, count);
endRemoveRows();
rc = true;
}
return rc;
}
Qt::ItemFlags SplitModel::flags(const QModelIndex& index) const
{
Qt::ItemFlags flags;
if(!index.isValid())
return flags;
if(index.row() < 0 || index.row() >= d->m_splits.count())
return flags;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}
#if 0
void SplitModel::removeSplit(const LedgerTransaction& t)
{
QModelIndexList list = match(index(0, 0), TransactionSplitIdRole, t.transactionSplitId(), -1, Qt::MatchExactly);
if(list.count()) {
QModelIndex index = list.at(0);
beginRemoveRows(QModelIndex(), index.row(), index.row());
delete d->m_ledgerItems[index.row()];
d->m_ledgerItems.remove(index.row(), 1);
endRemoveRows();
// just make sure we're in sync
Q_ASSERT(d->m_ledgerItems.count() == rowCount());
}
}
#endif
diff --git a/kmymoney/models/ledgermodel.h b/kmymoney/models/ledgermodel.h
index 66b34af3f..8f13a588b 100644
--- a/kmymoney/models/ledgermodel.h
+++ b/kmymoney/models/ledgermodel.h
@@ -1,610 +1,610 @@
/***************************************************************************
ledgermodel.cpp
-------------------
begin : Sat Aug 8 2015
copyright : (C) 2015 by Thomas Baumgart
email : Thomas Baumgart <tbaumgart@kde.org>
***************************************************************************/
/***************************************************************************
* *
* 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 LEDGERMODEL_H
#define LEDGERMODEL_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QAbstractTableModel>
#include <QSortFilterProxyModel>
#include <QScopedPointer>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneytransaction.h"
#include "mymoneyaccount.h"
#include "mymoneysplit.h"
#include "mymoneyschedule.h"
-#include "mymoneyfile.h"
+#include "mymoneyenums.h"
/**
* Forward declarations for the returned models.
*/
class LedgerItem
{
public:
explicit LedgerItem();
virtual ~LedgerItem();
/**
* This method returns the complete raw transaction from the engine
*/
virtual MyMoneyTransaction transaction() const = 0;
/**
* This method returns the complete raw split from the engine
*/
virtual const MyMoneySplit& split() const = 0;
/**
* Returns the postDate of the object. This can be used for sorting purposes.
*/
virtual QDate postDate() const = 0;
/**
* Returns the account id that this entry references. Default is
* to return an empty string.
*/
virtual QString accountId() const { return QString(); }
/**
* Returns the id of the counter account. If no counter split is present,
* it returns an empty string, in case more than one counter
* split is present it returns the fixed string '????'.
* @todo figure out how to handle the three+ split case.
*/
virtual QString counterAccountId() const = 0;
/**
* Returns the cost center id. This depends on how many slits the transaction has:
*
* two splits - returns the costcenter entry which is set in one of the splits
* otherwise - returns the costcenter entry of the split
*/
virtual QString costCenterId() const = 0;
/**
* Returns the full name and hierarchiy of the account.
*/
virtual QString account() const = 0;
/**
* Returns the full name and hierarchiy of the counter account. If no counter
* split is present, it returns an empty string, in case more than one counter
* split is present it returns the fixed string 'Split transaction'.
*/
virtual QString counterAccount() const = 0;
/**
* Returns the name of the payee that is assigned to the split
* or one that is found with other splits.
*/
virtual QString payeeName() const = 0;
/**
* Returns the id of the payee that is assigned to the split
* or one that is found with other splits.
*/
virtual QString payeeId() const = 0;
/**
* Returns the number of the transaction assigned by the user/institution
*/
virtual QString transactionNumber() const = 0;
/**
* Return information if this item is selectable, editable, etc.
* @sa QAbstractItemModel::flags()
*/
virtual Qt::ItemFlags flags() const = 0;
/**
* Returns an id for the selected transaction.
*/
virtual QString transactionId() const = 0;
/**
* Returns an id for the selected transaction and split.
*/
virtual QString transactionSplitId() const = 0;
/**
* Returns the number of splits in this transaction
*/
virtual int splitCount() const = 0;
/**
* Returns the internal reconciliation status for the selected transaction and split.
*/
virtual MyMoneySplit::reconcileFlagE reconciliationState() const = 0;
/**
* Returns the short reconciliation status text for the selected transaction and split.
*/
virtual QString reconciliationStateShort() const = 0;
/**
* Returns the full reconciliation status text for the selected transaction and split.
*/
virtual QString reconciliationStateLong() const = 0;
/**
* Returns the display string for the payment column.
*/
virtual QString payment() const = 0;
/**
* Returns the display string for the deposit column.
*/
virtual QString deposit() const = 0;
/**
* Allows to set the display string for the balance column.
*/
virtual void setBalance(QString txt) = 0;
/**
* Returns the display string for the balance column.
*/
virtual QString balance() const = 0;
/**
* Returns the amount of the shares of the split
*/
virtual MyMoneyMoney shares() const = 0;
/**
* Returns the amount of the shares of the split as QString (always positive)
*/
virtual QString sharesAmount() const = 0;
/**
* Returns the amount of the shares of the split as QString (with sign)
*/
virtual QString signedSharesAmount() const = 0;
/**
* Returns the suffix of the shares (Dr. or Cr.)
*/
virtual QString sharesSuffix() const = 0;
/**
* Returns the value in the transaction commodity of the split
*/
virtual MyMoneyMoney value() const = 0;
/**
* Returns the lines of a memo
*/
virtual QString memo() const = 0;
/**
* Returns true if an item is erroneous
*/
virtual bool isErroneous() const = 0;
/**
* Returns true if an item is imported
*/
virtual bool isImported() const = 0;
/**
* Returns true if this is the empty entry at the end of the ledger
*/
virtual bool isNewTransactionEntry() const = 0;
/**
* Returns the symbol of the commodity this transaction is kept in
*/
virtual QString transactionCommodity() const = 0;
};
class LedgerTransaction : public LedgerItem
{
public:
explicit LedgerTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s);
virtual ~LedgerTransaction();
static LedgerTransaction newTransactionEntry();
/// @copydoc LedgerItem::postDate()
virtual QDate postDate() const;
/// @copydoc LedgerItem::transaction()
MyMoneyTransaction transaction() const { return m_transaction; }
/// @copydoc LedgerItem::split()
const MyMoneySplit& split() const { return m_split; }
/// @copydoc LedgerItem::accountId()
virtual QString accountId() const { return m_split.accountId(); }
/// @copydoc LedgerItem::account()
virtual QString account() const { return m_account; }
/// @copydoc LedgerItem::counterAccountId()
virtual QString counterAccountId() const { return m_counterAccountId; }
/// @copydoc LedgerItem::counterAccount()
virtual QString counterAccount() const { return m_counterAccount; }
/// @copydoc LedgerItem::costCenterId()
virtual QString costCenterId() const { return m_costCenterId; }
/// @copydoc LedgerItem::payeeName()
virtual QString payeeName() const { return m_payeeName; }
/// @copydoc LedgerItem::payeeId()
virtual QString payeeId() const { return m_payeeId; }
/// @copydoc LedgerItem::transactionNumber()
virtual QString transactionNumber() const { return m_split.number(); }
/// @copydoc LedgerItem::flags()
virtual Qt::ItemFlags flags() const {return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; }
/// @copydoc LedgerItem::transactionSplitId()
virtual QString transactionSplitId() const;
/// @copydoc LedgerItem::splitCount()
virtual int splitCount() const { return m_transaction.splitCount(); }
/// @copydoc LedgerItem::transactionId()
virtual QString transactionId() const { return m_transaction.id(); }
/// @copydoc LedgerItem::reconciliationState()
virtual MyMoneySplit::reconcileFlagE reconciliationState() const;
/// @copydoc LedgerItem::reconciliationStateShort()
virtual QString reconciliationStateShort() const;
/// @copydoc LedgerItem::reconciliationStateShort()
virtual QString reconciliationStateLong() const;
/// @copydoc LedgerItem::payment()
virtual QString payment() const { return m_payment; }
/// @copydoc LedgerItem::deposit()
virtual QString deposit() const { return m_deposit; }
/// @copydoc LedgerItem::setBalance()
virtual void setBalance(QString txt);
/// @copydoc LedgerItem::balance()
virtual QString balance() const { return m_balance; }
/// @copydoc LedgerItem::shares()
virtual MyMoneyMoney shares() const { return m_split.shares(); }
/// @copydoc LedgerItem::sharesAmount()
virtual QString sharesAmount() const { return m_shares; }
/// @copydoc LedgerItem::signedSharesAmount()
virtual QString signedSharesAmount() const { return m_signedShares; }
/// @copydoc LedgerItem::sharesSuffix()
virtual QString sharesSuffix() const { return m_sharesSuffix; }
/// @copydoc LedgerItem::value()
virtual MyMoneyMoney value() const { return m_split.value(); }
/// @copydoc LedgerItem::memo()
virtual QString memo() const;
/// @copydoc LedgerItem::isErroneous()
virtual bool isErroneous() const { return m_erroneous; }
/// @copydoc LedgerItem::isImported()
virtual bool isImported() const { return m_transaction.isImported(); }
/// @copydoc LedgerItem::isNewTransactionEntry()
virtual bool isNewTransactionEntry() const;
/// @copydoc LedgerItem::transactionCommodity()
virtual QString transactionCommodity() const;
protected:
void setupValueDisplay();
protected:
MyMoneyTransaction m_transaction;
MyMoneySplit m_split;
QString m_counterAccountId;
QString m_counterAccount;
QString m_account;
QString m_costCenterId;
QString m_payeeName;
QString m_payeeId;
QString m_shares;
QString m_signedShares;
QString m_payment;
QString m_deposit;
QString m_balance;
QString m_sharesSuffix; // shows Cr or Dr
bool m_erroneous;
};
class LedgerSchedule : public LedgerTransaction
{
public:
explicit LedgerSchedule(const MyMoneySchedule& s, const MyMoneyTransaction& t, const MyMoneySplit& sp);
virtual ~LedgerSchedule();
/// @copydoc LedgerItem::transactionSplitId()
virtual QString transactionSplitId() const;
QString scheduleId() const;
/// @copydoc LedgerItem::isImported()
virtual bool isImported() const { return false; }
private:
MyMoneySchedule m_schedule;
};
class LedgerSplit : public LedgerTransaction
{
public:
explicit LedgerSplit(const MyMoneyTransaction& t, const MyMoneySplit& s);
virtual ~LedgerSplit();
/// @copydoc LedgerItem::memo()
virtual QString memo() const;
};
namespace LedgerRole {
enum Roles {
// Roles returning values
PostDateRole = Qt::UserRole,
PayeeNameRole,
AccountRole,
CounterAccountRole,
SplitCountRole,
ReconciliationRole,
ReconciliationRoleShort,
ReconciliationRoleLong,
SplitSharesRole,
ShareAmountRole,
ShareAmountSuffixRole,
SplitValueRole,
MemoRole,
SingleLineMemoRole,
NumberRole,
ErroneousRole,
ImportRole,
SplitRole,
TransactionRole,
// Roles returning ids
TransactionIdRole,
SplitIdRole,
TransactionSplitIdRole,
PayeeIdRole,
AccountIdRole,
CounterAccountIdRole,
CostCenterIdRole,
ScheduleIdRole,
TransactionCommodityRole,
// A pseudo role to emit the dataChanged() signal when
// used with setData()
EmitDataChangedRole
};
} // namespace LedgerRole
/**
*/
class LedgerModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit LedgerModel(QObject* parent = 0);
virtual ~LedgerModel();
enum Columns {
NumberColumn = 0,
DateColumn,
SecurityColumn,
CostCenterColumn,
DetailColumn,
ReconciliationColumn,
PaymentColumn,
DepositColumn,
QuantityColumn,
PriceColumn,
AmountColumn,
ValueColumn,
BalanceColumn,
// insert new columns above this line
NumberOfLedgerColumns
};
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
virtual Qt::ItemFlags flags(const QModelIndex& index) const;
virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
/**
* clears all objects currently in the model
*/
void unload();
/**
* Adds the transaction items in the @a list to the model
*/
void addTransactions(const QList<QPair<MyMoneyTransaction, MyMoneySplit> >& list);
/**
* Adds a single transaction @a t to the model
*/
void addTransaction(const LedgerTransaction& t);
/**
* Adds a single split based on its transactionSplitId
*/
void addTransaction(const QString& transactionSplitId);
/**
* Adds the schedule items in the @a list to the model
*/
void addSchedules(const QList< MyMoneySchedule >& list, int previewPeriod);
/**
* Loads the model with data from the engine
*/
void load();
/**
* This method extracts the transaction id from a combined
* transactionSplitId and returns it. In case the @a transactionSplitId does
* not resembles a transactionSplitId an empty string is returned.
*/
QString transactionIdFromTransactionSplitId(const QString& transactionSplitId) const;
public Q_SLOTS:
protected Q_SLOTS:
- void removeTransaction(MyMoneyFile::notificationObjectT objType, const QString& id);
- void addTransaction(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void modifyTransaction(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void removeSchedule(MyMoneyFile::notificationObjectT objType, const QString& id);
- void addSchedule(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void modifySchedule(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
+ void removeTransaction(eMyMoney::File::Object objType, const QString& id);
+ void addTransaction(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void modifyTransaction(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void removeSchedule(eMyMoney::File::Object objType, const QString& id);
+ void addSchedule(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void modifySchedule(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
protected:
struct Private;
QScopedPointer<Private> d;
};
class LedgerSortFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
LedgerSortFilterProxyModel(QObject* parent = 0);
virtual ~LedgerSortFilterProxyModel();
void setShowNewTransaction(bool show);
- void setAccountType(MyMoneyAccount::accountTypeE type);
+ void setAccountType(eMyMoney::Account type);
/**
* This method maps the @a index to the base model and calls setData on it
*/
virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
/**
* This method returns the headerData adjusted to the current
* accountType
*/
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
protected:
virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const;
virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
private:
bool m_showNewTransaction;
- MyMoneyAccount::accountTypeE m_accountType;
+ eMyMoney::Account m_accountType;
};
class SplitModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum Columns {
NumberColumn = 0,
DateColumn,
SecurityColumn,
CostCenterColumn,
DetailColumn,
ReconciliationColumn,
PaymentColumn,
DepositColumn,
QuantityColumn,
PriceColumn,
AmountColumn,
ValueColumn,
BalanceColumn,
// insert new columns above this line
NumberOfLedgerColumns
};
explicit SplitModel(QObject* parent = 0);
virtual ~SplitModel();
void deepCopy(const SplitModel& right, bool revertSplitSign = false);
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
virtual Qt::ItemFlags flags(const QModelIndex& index) const;
/**
* Adds a single split @a t to the model
*/
void addSplit(const QString& transactionSplitId);
/**
* Adds a single dummy split to the model which is used for
* createion of new splits.
*/
void addEmptySplitEntry();
/**
* Remove the single dummy split to the model which is used for
* createion of new splits from the model.
*/
void removeEmptySplitEntry();
virtual bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
/**
* This method returns the string to be used for new split ids
*/
static QString newSplitId();
/**
* This method compares the @a id against the one provided
* by newSplitId() and returns true if it is identical.
*/
static bool isNewSplitId(const QString& id);
// void removeSplit(const LedgerTransaction& t);
private:
struct Private;
QScopedPointer<Private> d;
};
#endif // LEDGERMODEL_H
diff --git a/kmymoney/models/onlinejobmodel.cpp b/kmymoney/models/onlinejobmodel.cpp
index db0c2a701..cc5eb242e 100644
--- a/kmymoney/models/onlinejobmodel.cpp
+++ b/kmymoney/models/onlinejobmodel.cpp
@@ -1,272 +1,274 @@
/*
* This file is part of KMyMoney, A Personal Finance Manager by KDE
* Copyright (C) 2013-2015 Christian Dávid <christian-david@web.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "onlinejobmodel.h"
#include <QIcon>
#include <KLocalizedString>
+#include "mymoneyobject.h"
+#include "mymoneyfile.h"
#include "mymoneyutils.h"
#include "onlinetasks/interfaces/tasks/onlinetask.h"
#include "onlinetasks/interfaces/tasks/credittransfer.h"
#include "mymoney/onlinejobtyped.h"
#include "payeeidentifier.h"
#include "payeeidentifiertyped.h"
#include "ibanbic.h"
#include "icons/icons.h"
using namespace Icons;
onlineJobModel::onlineJobModel(QObject *parent) :
QAbstractTableModel(parent),
m_jobIdList(QStringList())
{
MyMoneyFile *const file = MyMoneyFile::instance();
connect(file, &MyMoneyFile::objectAdded,
this, &onlineJobModel::slotObjectAdded);
connect(file, &MyMoneyFile::objectModified,
this, &onlineJobModel::slotObjectModified);
connect(file, &MyMoneyFile::objectRemoved,
this, &onlineJobModel::slotObjectRemoved);
}
void onlineJobModel::load()
{
unload();
beginInsertRows(QModelIndex(), 0, 0);
foreach (const onlineJob job, MyMoneyFile::instance()->onlineJobList()) {
m_jobIdList.append(job.id());
}
endInsertRows();
}
void onlineJobModel::unload()
{
if (!m_jobIdList.isEmpty()) {
beginResetModel();
m_jobIdList.clear();
endResetModel();
}
}
int onlineJobModel::rowCount(const QModelIndex & parent) const
{
if (parent.isValid())
return 0;
return m_jobIdList.count();
}
int onlineJobModel::columnCount(const QModelIndex & parent) const
{
if (parent.isValid())
return 0;
return 4;
}
QVariant onlineJobModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
switch (section) {
case columns::ColAccount: return i18n("Account");
case columns::ColAction: return i18n("Action");
case columns::ColDestination: return i18n("Destination");
case columns::ColValue: return i18n("Value");
}
}
return QVariant();
}
/**
* @todo LOW improve speed
* @todo use now onlineJob system
*/
QVariant onlineJobModel::data(const QModelIndex & index, int role) const
{
if (index.parent().isValid())
return QVariant();
Q_ASSERT(m_jobIdList.length() > index.row());
onlineJob job;
try {
job = MyMoneyFile::instance()->getOnlineJob(m_jobIdList[index.row()]);
} catch (const MyMoneyException&) {
return QVariant();
}
// id of MyMoneyObject
if (role == roles::OnlineJobId)
return QVariant::fromValue(job.id());
else if (role == roles::OnlineJobRole)
return QVariant::fromValue(job);
// If job is null, display an error message and exit
if (job.isNull()) {
if (index.column() == columns::ColAction) {
switch (role) {
case Qt::DisplayRole: return i18n("Not able to display this job.");
case Qt::ToolTipRole: return i18n("Could not find a plugin to display this job or it does not contain any data.");
}
}
return QVariant();
}
// Show general information
if (index.column() == columns::ColAccount) {
// Account column
if (role == Qt::DisplayRole) {
return QVariant::fromValue(job.responsibleMyMoneyAccount().name());
} else if (role == Qt::DecorationRole) {
if (job.isLocked())
return QIcon::fromTheme(g_Icons[Icon::TaskOngoing]);
switch (job.bankAnswerState()) {
case onlineJob::acceptedByBank: return QIcon::fromTheme(g_Icons[Icon::TaskComplete]);
case onlineJob::sendingError:
case onlineJob::abortedByUser:
case onlineJob::rejectedByBank: return QIcon::fromTheme(g_Icons[Icon::TaskReject]);
case onlineJob::noBankAnswer: break;
}
if (job.sendDate().isValid()) {
return QIcon::fromTheme(g_Icons[Icon::TaskAccepted]);
} else if (!job.isValid()) {
return QIcon::fromTheme(g_Icons[Icon::TaskAttention]);
}
} else if (role == Qt::ToolTipRole) {
if (job.isLocked())
return i18n("Job is being processed at the moment.");
switch (job.bankAnswerState()) {
case onlineJob::acceptedByBank: return i18nc("Arg 1 is a date/time", "This job was accepted by the bank on %1.").arg(job.bankAnswerDate().toString(Qt::DefaultLocaleShortDate));
case onlineJob::sendingError: return i18nc("Arg 1 is a date/time", "Sending this job failed (tried on %1).").arg(job.sendDate().toString(Qt::DefaultLocaleShortDate));
case onlineJob::abortedByUser: return i18n("Sending this job was manually aborted.");
case onlineJob::rejectedByBank: return i18nc("Arg 1 is a date/time", "The bank rejected this job on %1.").arg(job.bankAnswerDate().toString(Qt::DefaultLocaleShortDate));
case onlineJob::noBankAnswer:
if (job.sendDate().isValid())
return i18nc("Arg 1 is a date/time", "The bank accepted this job on %1.").arg(job.sendDate().toString(Qt::DefaultLocaleShortDate));
else if (!job.isValid())
return i18n("This job needs further editing and cannot be sent therefore.");
else
return i18n("This job is ready for sending.");
}
}
return QVariant();
} else if (index.column() == columns::ColAction) {
if (role == Qt::DisplayRole)
return QVariant::fromValue(job.task()->jobTypeName());
return QVariant();
}
// Show credit transfer data
try {
onlineJobTyped<creditTransfer> transfer(job);
if (index.column() == columns::ColValue) {
if (role == Qt::DisplayRole)
return QVariant::fromValue(MyMoneyUtils::formatMoney(transfer.task()->value(), transfer.task()->currency()));
} else if (index.column() == columns::ColDestination) {
if (role == Qt::DisplayRole) {
const payeeIdentifierTyped<payeeIdentifiers::ibanBic> ibanBic(transfer.constTask()->beneficiary());
return QVariant(ibanBic->ownerName());
}
}
} catch (MyMoneyException&) {
} catch (payeeIdentifier::exception&) {
}
return QVariant();
}
void onlineJobModel::reloadAll()
{
emit dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, columnCount() - 1));
}
/**
* This method removes the rows from MyMoneyFile.
*/
bool onlineJobModel::removeRow(int row, const QModelIndex& parent)
{
if (parent.isValid())
return false;
Q_ASSERT(m_jobIdList.count() < row);
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyFileTransaction transaction;
const onlineJob job = file->getOnlineJob(m_jobIdList[row]);
file->removeOnlineJob(job);
transaction.commit();
return true;
}
/**
* This method removes the rows from MyMoneyFile.
*/
bool onlineJobModel::removeRows(int row, int count, const QModelIndex & parent)
{
if (parent.isValid())
return false;
Q_ASSERT(m_jobIdList.count() > row);
Q_ASSERT(m_jobIdList.count() >= (row + count));
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyFileTransaction transaction;
for (int i = 0; i < count; ++i) {
const onlineJob job = file->getOnlineJob(m_jobIdList[row+i]);
file->removeOnlineJob(job);
}
transaction.commit();
return true;
}
-void onlineJobModel::slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj)
+void onlineJobModel::slotObjectAdded(eMyMoney::File::Object objType, const MyMoneyObject * const obj)
{
- if (Q_LIKELY(objType != MyMoneyFile::notifyOnlineJob))
+ if (Q_LIKELY(objType != eMyMoney::File::Object::OnlineJob))
return;
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_jobIdList.append(obj->id());
endInsertRows();
}
-void onlineJobModel::slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj)
+void onlineJobModel::slotObjectModified(eMyMoney::File::Object objType, const MyMoneyObject * const obj)
{
- if (Q_LIKELY(objType != MyMoneyFile::notifyOnlineJob))
+ if (Q_LIKELY(objType != eMyMoney::File::Object::OnlineJob))
return;
int row = m_jobIdList.indexOf(obj->id());
if (row != -1)
emit dataChanged(index(row, 0), index(row, columnCount() - 1));
}
-void onlineJobModel::slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id)
+void onlineJobModel::slotObjectRemoved(eMyMoney::File::Object objType, const QString& id)
{
- if (Q_LIKELY(objType != MyMoneyFile::notifyOnlineJob))
+ if (Q_LIKELY(objType != eMyMoney::File::Object::OnlineJob))
return;
int row = m_jobIdList.indexOf(id);
if (row != -1) {
m_jobIdList.removeAll(id);
beginRemoveRows(QModelIndex(), row, row);
endRemoveRows();
}
}
diff --git a/kmymoney/models/onlinejobmodel.h b/kmymoney/models/onlinejobmodel.h
index d1c714010..a03a2dd11 100644
--- a/kmymoney/models/onlinejobmodel.h
+++ b/kmymoney/models/onlinejobmodel.h
@@ -1,79 +1,80 @@
/*
* This file is part of KMyMoney, A Personal Finance Manager by KDE
* Copyright (C) 2014-2015 Christian Dávid <christian-david@web.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ONLINEJOBMODEL_H
#define ONLINEJOBMODEL_H
#include <QAbstractTableModel>
#include <QStringList>
-#include "mymoney/mymoneyfile.h"
+#include "mymoneyenums.h"
+class MyMoneyObject;
class onlineJobModel : public QAbstractTableModel
{
Q_OBJECT
public:
/**
* @brief Item Data roles for onlineJobs
* In addition to Qt::ItemDataRole
*/
enum roles {
OnlineJobId = Qt::UserRole, /**< QString of onlineJob.id() */
OnlineJobRole /**< the real onlineJob */
};
enum columns {
ColAccount,
ColAction,
ColDestination,
ColValue
};
int rowCount(const QModelIndex & parent = QModelIndex()) const;
int columnCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
QVariant headerData(int section, Qt::Orientation orientation , int role = Qt::DisplayRole) const;
/** @brief Remove onlineJob identified by row */
bool removeRow(int row, const QModelIndex & parent = QModelIndex());
/** @brief Remove onlineJobs identified by row and count */
bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex());
signals:
public slots:
void reloadAll();
- void slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id);
+ void slotObjectAdded(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void slotObjectModified(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void slotObjectRemoved(eMyMoney::File::Object objType, const QString& id);
/** @brief Load data from MyMoneyFile */
void load();
void unload();
protected:
/** Only @ref Models should be able to construct this class */
explicit onlineJobModel(QObject *parent = 0);
friend class Models;
private:
QStringList m_jobIdList;
};
#endif // ONLINEJOBMODEL_H
diff --git a/kmymoney/models/payeesmodel.cpp b/kmymoney/models/payeesmodel.cpp
index 8e201ed15..85f5d918a 100644
--- a/kmymoney/models/payeesmodel.cpp
+++ b/kmymoney/models/payeesmodel.cpp
@@ -1,163 +1,164 @@
/***************************************************************************
* payeesmodel.cpp
* -------------------
* begin : Mon Oct 03 2016
* copyright : (C) 2016 by Thomas Baumgart
* email : Thomas Baumgart <tbaumgart@kde.org>
***************************************************************************/
/***************************************************************************
* *
* 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 "payeesmodel.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QDebug>
#include <QString>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneypayee.h"
struct PayeesModel::Private
{
Private() {}
QVector<MyMoneyPayee*> m_payeeItems;
};
PayeesModel::PayeesModel(QObject* parent)
: QAbstractListModel(parent)
, d(new Private)
{
qDebug() << "Payees model created with items" << d->m_payeeItems.count();
d->m_payeeItems.clear();
}
PayeesModel::~PayeesModel()
{
}
int PayeesModel::rowCount(const QModelIndex& parent) const
{
// since the ledger model is a simple table model, we only
// return the rowCount for the hiddenRootItem. and zero otherwise
if(parent.isValid()) {
return 0;
}
return d->m_payeeItems.count();
}
int PayeesModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return 1;
}
Qt::ItemFlags PayeesModel::flags(const QModelIndex& index) const
{
Qt::ItemFlags flags;
if(!index.isValid())
return flags;
if(index.row() < 0 || index.row() >= d->m_payeeItems.count())
return flags;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}
QVariant PayeesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch(section) {
case 0:
return i18n("Payee");
break;
}
}
return QAbstractItemModel::headerData(section, orientation, role);
}
QVariant PayeesModel::data(const QModelIndex& index, int role) const
{
if(!index.isValid())
return QVariant();
if(index.row() < 0 || index.row() >= d->m_payeeItems.count())
return QVariant();
QVariant rc;
switch(role) {
case Qt::DisplayRole:
case Qt::EditRole:
// make sure to never return any displayable text for the dummy entry
if(!d->m_payeeItems[index.row()]->id().isEmpty()) {
rc = d->m_payeeItems[index.row()]->name();
} else {
rc = QString();
}
break;
case Qt::TextAlignmentRole:
rc = QVariant(Qt::AlignLeft | Qt::AlignTop);
break;
case PayeeIdRole:
rc = d->m_payeeItems[index.row()]->id();
break;
}
return rc;
}
bool PayeesModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if(!index.isValid()) {
return false;
}
qDebug() << "setData(" << index.row() << index.column() << ")" << value << role;
return QAbstractItemModel::setData(index, value, role);
}
void PayeesModel::unload()
{
if(rowCount() > 0) {
beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
qDeleteAll(d->m_payeeItems);
d->m_payeeItems.clear();
QVector<MyMoneyPayee*> swp;
d->m_payeeItems.swap(swp); // changed behaviour from Qt 5.7 http://doc.qt.io/qt-5/qvector.html#clear
endRemoveRows();
}
}
void PayeesModel::load()
{
const QList<MyMoneyPayee> list = MyMoneyFile::instance()->payeeList();
if(list.count() > 0) {
beginInsertRows(QModelIndex(), rowCount(), rowCount() + list.count());
// create an empty entry for those items that do not reference a cost center
d->m_payeeItems.append(new MyMoneyPayee());
foreach (const auto it, list)
d->m_payeeItems.append(new MyMoneyPayee(it));
endInsertRows();
}
}
diff --git a/kmymoney/models/securitiesmodel.cpp b/kmymoney/models/securitiesmodel.cpp
index 49f595b78..2672a2dd6 100644
--- a/kmymoney/models/securitiesmodel.cpp
+++ b/kmymoney/models/securitiesmodel.cpp
@@ -1,368 +1,371 @@
/***************************************************************************
securitiesmodel.cpp
-------------------
copyright : (C) 2017 by Łukasz Wojniłowicz <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 "securitiesmodel.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QMenu>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
+#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
+
class SecuritiesModel::Private
{
public:
Private() : m_file(MyMoneyFile::instance())
{
QVector<Column> columns {
Column::Security, Column::Symbol, Column::Type,
Column::Market, Column::Currency, Column::Fraction
};
foreach (auto const column, columns)
m_columns.append(column);
}
~Private() {}
void loadSecurity(QStandardItem *node, const MyMoneySecurity &sec)
{
auto itSec = new QStandardItem(sec.name());
node->appendRow(itSec);
itSec->setEditable(false);
setSecurityData(node, itSec->row(), sec, m_columns);
}
void setSecurityData(QStandardItem *node, const int row, const MyMoneySecurity &security, const QList<Column> &columns)
{
QStandardItem *cell;
auto getCell = [&, row](const auto column) {
cell = node->child(row, column); // try to get QStandardItem
if (!cell) { // it may be uninitialized
cell = new QStandardItem; // so create one
node->setChild(row, column, cell); // and add it under the node
cell->setEditable(false); // and don't forget that it's non-editable
}
};
auto colNum = m_columns.indexOf(Column::Security);
if (colNum == -1)
return;
// Security
getCell(colNum);
if (columns.contains(Column::Security)) {
cell->setData(security.name(), Qt::DisplayRole);
cell->setData(security.id(), Qt::UserRole);
}
// Symbol
if (columns.contains(Column::Symbol)) {
colNum = m_columns.indexOf(Column::Symbol);
if (colNum != -1) {
getCell(colNum);
cell->setData(security.tradingSymbol(), Qt::DisplayRole);
}
}
// Type
if (columns.contains(Column::Type)) {
colNum = m_columns.indexOf(Column::Type);
if (colNum != -1) {
getCell(colNum);
cell->setData(security.securityTypeToString(security.securityType()), Qt::DisplayRole);
}
}
// Market
if (columns.contains(Column::Market)) {
colNum = m_columns.indexOf(Column::Market);
if (colNum != -1) {
getCell(colNum);
QString market;
if (security.isCurrency())
market = QLatin1String("ISO 4217");
else
market = security.tradingMarket();
cell->setData(market, Qt::DisplayRole);
}
}
// Currency
if (columns.contains(Column::Currency)) {
colNum = m_columns.indexOf(Column::Currency);
if (colNum != -1) {
getCell(colNum);
MyMoneySecurity tradingCurrency;
if (!security.isCurrency())
tradingCurrency = m_file->security(security.tradingCurrency());
cell->setData(tradingCurrency.tradingSymbol(), Qt::DisplayRole);
}
}
// Fraction
if (columns.contains(Column::Fraction)) {
colNum = m_columns.indexOf(Column::Fraction);
if (colNum != -1) {
getCell(colNum);
cell->setData(QString::number(security.smallestAccountFraction()), Qt::DisplayRole);
}
}
}
QStandardItem *itemFromSecurityId(QStandardItemModel *model, const QString &securityId)
{
const auto itemList = model->match(model->index(0, 0), Qt::UserRole, QVariant(securityId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
if (!itemList.isEmpty())
return model->itemFromIndex(itemList.first());
return nullptr;
}
MyMoneyFile *m_file;
QList<SecuritiesModel::Column> m_columns;
QStandardItem *m_ndCurrencies;
QStandardItem *m_ndSecurities;
};
SecuritiesModel::SecuritiesModel(QObject *parent)
: QStandardItemModel(parent), d(new Private)
{
init();
}
SecuritiesModel::~SecuritiesModel()
{
delete d;
}
void SecuritiesModel::init()
{
QStringList headerLabels;
foreach (const auto column, d->m_columns)
headerLabels.append(getHeaderName(column));
setHorizontalHeaderLabels(headerLabels);
}
void SecuritiesModel::load()
{
this->blockSignals(true);
auto rootItem = invisibleRootItem();
auto secList = d->m_file->securityList(); // get all available securities
d->m_ndSecurities = new QStandardItem(QStringLiteral("Securities"));
d->m_ndSecurities->setEditable(false);
rootItem->appendRow(d->m_ndSecurities);
foreach (const auto sec, secList)
d->loadSecurity(d->m_ndSecurities, sec);
secList = d->m_file->currencyList(); // get all available currencies
d->m_ndCurrencies = new QStandardItem(QStringLiteral("Currencies"));
d->m_ndCurrencies->setEditable(false);
rootItem->appendRow(d->m_ndCurrencies);
foreach (const auto sec, secList)
d->loadSecurity(d->m_ndCurrencies, sec);
this->blockSignals(false);
}
/**
* Notify the model that an object has been added. An action is performed only if the object is a security.
*
*/
-void SecuritiesModel::slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj)
+void SecuritiesModel::slotObjectAdded(eMyMoney::File::Object objType, const MyMoneyObject * const obj)
{
// check whether change is about security
- if (objType != MyMoneyFile::notifySecurity)
+ if (objType != eMyMoney::File::Object::Security)
return;
// check that we're about to add security
auto sec = dynamic_cast<const MyMoneySecurity * const>(obj);
if (!sec)
return;
auto itSec = d->itemFromSecurityId(this, sec->id());
QStandardItem *node;
if (sec->isCurrency())
node = d->m_ndCurrencies;
else
node = d->m_ndSecurities;
// if security doesn't exist in model then add it
if (!itSec) {
itSec = new QStandardItem(sec->name());
node->appendRow(itSec);
itSec->setEditable(false);
}
d->setSecurityData(node, itSec->row(), *sec, d->m_columns);
}
/**
* Notify the model that an object has been modified. An action is performed only if the object is a security.
*
*/
-void SecuritiesModel::slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj)
+void SecuritiesModel::slotObjectModified(eMyMoney::File::Object objType, const MyMoneyObject * const obj)
{
- if (objType != MyMoneyFile::notifySecurity)
+ if (objType != eMyMoney::File::Object::Security)
return;
// check that we're about to modify security
auto sec = dynamic_cast<const MyMoneySecurity * const>(obj);
if (!sec)
return;
auto itSec = d->itemFromSecurityId(this, sec->id());
QStandardItem *node;
if (sec->isCurrency())
node = d->m_ndCurrencies;
else
node = d->m_ndSecurities;
d->setSecurityData(node, itSec->row(), *sec, d->m_columns);
}
/**
* Notify the model that an object has been removed. An action is performed only if the object is an account.
*
*/
-void SecuritiesModel::slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id)
+void SecuritiesModel::slotObjectRemoved(eMyMoney::File::Object objType, const QString& id)
{
- if (objType != MyMoneyFile::notifySecurity)
+ if (objType != eMyMoney::File::Object::Security)
return;
const auto indexList = match(index(0, 0), Qt::UserRole, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive));
foreach (const auto index, indexList)
removeRow(index.row(), index.parent());
}
auto SecuritiesModel::getColumns()
{
return &d->m_columns;
}
QString SecuritiesModel::getHeaderName(const Column column)
{
switch(column) {
case Security:
return i18n("Security");
case Symbol:
return i18n("Symbol");
case Type:
return i18n("Type");
case Market:
return i18n("Market");
case Currency:
return i18n("Currency");
case Fraction:
return i18n("Fraction");
default:
return QString();
}
}
class SecuritiesFilterProxyModel::Private
{
public:
Private() :
m_mdlColumns(nullptr),
m_file(MyMoneyFile::instance())
{}
~Private() {}
QList<SecuritiesModel::Column> *m_mdlColumns;
QList<SecuritiesModel::Column> m_visColumns;
MyMoneyFile *m_file;
};
SecuritiesFilterProxyModel::SecuritiesFilterProxyModel(QObject *parent, SecuritiesModel *model, const QList<SecuritiesModel::Column> &columns)
: KRecursiveFilterProxyModel(parent), d(new Private)
{
setDynamicSortFilter(true);
setFilterKeyColumn(-1);
setSortLocaleAware(true);
setFilterCaseSensitivity(Qt::CaseInsensitive);
setSourceModel(model);
d->m_mdlColumns = model->getColumns();
d->m_visColumns.append(columns);
}
SecuritiesFilterProxyModel::~SecuritiesFilterProxyModel()
{
delete d;
}
bool SecuritiesFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
if (d->m_visColumns.isEmpty() || d->m_visColumns.contains(d->m_mdlColumns->at(source_column)))
return true;
return false;
}
QList<SecuritiesModel::Column> &SecuritiesFilterProxyModel::getVisibleColumns()
{
return d->m_visColumns;
}
void SecuritiesFilterProxyModel::slotColumnsMenu(const QPoint)
{
// construct all hideable columns list
const QList<SecuritiesModel::Column> idColumns {
SecuritiesModel::Symbol, SecuritiesModel::Type,
SecuritiesModel::Market, SecuritiesModel::Currency,
SecuritiesModel::Fraction
};
// create menu
QMenu menu(i18n("Displayed columns"));
QList<QAction *> actions;
foreach (const auto idColumn, idColumns) {
auto a = new QAction(nullptr);
a->setObjectName(QString::number(idColumn));
a->setText(SecuritiesModel::getHeaderName(idColumn));
a->setCheckable(true);
a->setChecked(d->m_visColumns.contains(idColumn));
actions.append(a);
}
menu.addActions(actions);
// execute menu and get result
const auto retAction = menu.exec(QCursor::pos());
if (retAction) {
const auto idColumn = static_cast<SecuritiesModel::Column>(retAction->objectName().toInt());
const auto isChecked = retAction->isChecked();
const auto contains = d->m_visColumns.contains(idColumn);
if (isChecked && !contains) { // column has just been enabled
d->m_visColumns.append(idColumn); // change filtering variable
emit columnToggled(idColumn, true); // emit signal for method to add column to model
invalidate(); // refresh model to reflect recent changes
} else if (!isChecked && contains) { // column has just been disabled
d->m_visColumns.removeOne(idColumn);
emit columnToggled(idColumn, false);
invalidate();
}
}
}
diff --git a/kmymoney/models/securitiesmodel.h b/kmymoney/models/securitiesmodel.h
index 53f34bc5a..f824ed21e 100644
--- a/kmymoney/models/securitiesmodel.h
+++ b/kmymoney/models/securitiesmodel.h
@@ -1,89 +1,90 @@
/***************************************************************************
securitiesmodel.h
-------------------
copyright : (C) 2017 by Łukasz Wojniłowicz <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 SECURITIESMODEL_H
#define SECURITIESMODEL_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QStandardItemModel>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KItemModels/KRecursiveFilterProxyModel>
// ----------------------------------------------------------------------------
// Project Includes
-#include "mymoneyfile.h"
+#include "mymoneyenums.h"
+class MyMoneyObject;
class SecuritiesModel : public QStandardItemModel
{
Q_OBJECT
public:
enum Column { Security = 0, Symbol, Type, Market, Currency, Fraction };
~SecuritiesModel();
auto getColumns();
static QString getHeaderName(const Column column);
public slots:
- void slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj);
- void slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id);
+ void slotObjectAdded(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void slotObjectModified(eMyMoney::File::Object objType, const MyMoneyObject * const obj);
+ void slotObjectRemoved(eMyMoney::File::Object objType, const QString& id);
private:
SecuritiesModel(QObject *parent = nullptr);
SecuritiesModel(const SecuritiesModel&);
SecuritiesModel& operator=(SecuritiesModel&);
friend class Models; // only this class can create SecuritiesModel
void init();
void load();
protected:
class Private;
Private* const d;
};
class SecuritiesFilterProxyModel : public KRecursiveFilterProxyModel
{
Q_OBJECT
public:
SecuritiesFilterProxyModel(QObject *parent , SecuritiesModel *model, const QList<SecuritiesModel::Column> &columns = QList<SecuritiesModel::Column>());
~SecuritiesFilterProxyModel();
QList<SecuritiesModel::Column> &getVisibleColumns();
signals:
void columnToggled(const SecuritiesModel::Column column, const bool show);
public slots:
void slotColumnsMenu(const QPoint);
protected:
bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override;
private:
class Private;
Private* const d;
};
#endif // SECURITIESMODEL_H
diff --git a/kmymoney/mymoney/mymoneyaccount.cpp b/kmymoney/mymoney/mymoneyaccount.cpp
index 17a83bb7e..f281ea239 100644
--- a/kmymoney/mymoney/mymoneyaccount.cpp
+++ b/kmymoney/mymoney/mymoneyaccount.cpp
@@ -1,870 +1,874 @@
/***************************************************************************
mymoneyaccount.cpp
-------------------
copyright : (C) 2000 by Michael Edwardes <mte@users.sourceforge.net>
(C) 2002 by Thomas Baumgart <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 "mymoneyaccount.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QRegExp>
#include <QPixmap>
#include <QPixmapCache>
#include <QPainter>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyutils.h"
#include "mymoneyexception.h"
#include "mymoneysplit.h"
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
+#include "mymoneyinstitution.h"
+#include "mymoneypayee.h"
#include "payeeidentifier/payeeidentifiertyped.h"
#include "payeeidentifier/ibanandbic/ibanbic.h"
#include "payeeidentifier/nationalaccount/nationalaccount.h"
#include "mymoneystoragenames.h"
#include "icons/icons.h"
using namespace MyMoneyStorageNodes;
using namespace Icons;
+using namespace eMyMoney;
MyMoneyAccount::MyMoneyAccount() :
- m_accountType(UnknownAccountType),
+ m_accountType(Account::Unknown),
m_fraction(-1)
{
}
MyMoneyAccount::~MyMoneyAccount()
{
}
MyMoneyAccount::MyMoneyAccount(const QString& id, const MyMoneyAccount& right) :
MyMoneyObject(id)
{
*this = right;
setId(id);
}
MyMoneyAccount::MyMoneyAccount(const QDomElement& node) :
MyMoneyObject(node),
MyMoneyKeyValueContainer(node.elementsByTagName(nodeNames[nnKeyValuePairs]).item(0).toElement()),
- m_accountType(UnknownAccountType),
+ m_accountType(Account::Unknown),
m_fraction(-1)
{
if (nodeNames[nnAccount] != node.tagName())
throw MYMONEYEXCEPTION("Node was not ACCOUNT");
setName(node.attribute(getAttrName(anName)));
// qDebug("Reading information for account %s", acc.name().data());
setParentAccountId(QStringEmpty(node.attribute(getAttrName(anParentAccount))));
setLastModified(stringToDate(QStringEmpty(node.attribute(getAttrName(anLastModified)))));
setLastReconciliationDate(stringToDate(QStringEmpty(node.attribute(getAttrName(anLastReconciled)))));
if (!m_lastReconciliationDate.isValid()) {
// for some reason, I was unable to access our own kvp at this point through
// the value() method. It always returned empty strings. The workaround for
// this is to construct a local kvp the same way as we have done before and
// extract the value from it.
//
// Since we want to get rid of the lastStatementDate record anyway, this seems
// to be ok for now. (ipwizard - 2008-08-14)
QString txt = MyMoneyKeyValueContainer(node.elementsByTagName(nodeNames[nnKeyValuePairs]).item(0).toElement()).value("lastStatementDate");
if (!txt.isEmpty()) {
setLastReconciliationDate(QDate::fromString(txt, Qt::ISODate));
}
}
setInstitutionId(QStringEmpty(node.attribute(getAttrName(anInstitution))));
setNumber(QStringEmpty(node.attribute(getAttrName(anNumber))));
setOpeningDate(stringToDate(QStringEmpty(node.attribute(getAttrName(anOpened)))));
setCurrencyId(QStringEmpty(node.attribute(getAttrName(anCurrency))));
QString tmp = QStringEmpty(node.attribute(getAttrName(anType)));
bool bOK = false;
int type = tmp.toInt(&bOK);
if (bOK) {
- setAccountType(static_cast<MyMoneyAccount::accountTypeE>(type));
+ setAccountType(static_cast<Account>(type));
} else {
qWarning("XMLREADER: Account %s had invalid or no account type information.", qPrintable(name()));
}
if (node.hasAttribute(getAttrName(anOpeningBalance))) {
if (!MyMoneyMoney(node.attribute(getAttrName(anOpeningBalance))).isZero()) {
QString msg = i18n("Account %1 contains an opening balance. Please use KMyMoney version 0.8 or later and earlier than version 0.9 to correct the problem.", m_name);
throw MYMONEYEXCEPTION(msg);
}
}
setDescription(node.attribute(getAttrName(anDescription)));
m_id = QStringEmpty(node.attribute(getAttrName(anID)));
// qDebug("Account %s has id of %s, type of %d, parent is %s.", acc.name().data(), id.data(), type, acc.parentAccountId().data());
// Process any Sub-Account information found inside the account entry.
m_accountList.clear();
QDomNodeList nodeList = node.elementsByTagName(getElName(enSubAccounts));
if (nodeList.count() > 0) {
nodeList = nodeList.item(0).toElement().elementsByTagName(getElName(enSubAccount));
for (int i = 0; i < nodeList.count(); ++i) {
addAccountId(QString(nodeList.item(i).toElement().attribute(getAttrName(anID))));
}
}
nodeList = node.elementsByTagName(getElName(enOnlineBanking));
if (nodeList.count() > 0) {
QDomNamedNodeMap attributes = nodeList.item(0).toElement().attributes();
for (int i = 0; i < attributes.count(); ++i) {
const QDomAttr& it_attr = attributes.item(i).toAttr();
m_onlineBankingSettings.setValue(it_attr.name(), it_attr.value());
}
}
// Up to and including version 4.6.6 the new account dialog stored the iban in the kvp-key "IBAN".
// But the rest of the software uses "iban". So correct this:
if (!value("IBAN").isEmpty()) {
// If "iban" was not set, set it now. If it is set, the user reseted it already, so remove
// the garbage.
if (value(getAttrName(anIBAN)).isEmpty())
setValue(getAttrName(anIBAN), value("IBAN"));
deletePair("IBAN");
}
}
void MyMoneyAccount::setName(const QString& name)
{
m_name = name;
}
void MyMoneyAccount::setNumber(const QString& number)
{
m_number = number;
}
void MyMoneyAccount::setDescription(const QString& desc)
{
m_description = desc;
}
void MyMoneyAccount::setInstitutionId(const QString& id)
{
m_institution = id;
}
void MyMoneyAccount::setLastModified(const QDate& date)
{
m_lastModified = date;
}
void MyMoneyAccount::setOpeningDate(const QDate& date)
{
m_openingDate = date;
}
void MyMoneyAccount::setLastReconciliationDate(const QDate& date)
{
// FIXME: for a limited time (maybe until we delivered 1.0) we
// keep the last reconciliation date also in the KVP for backward
// compatibility. After that, the setValue() statemetn should be removed
// and the XML ctor should remove the value completely from the KVP
setValue("lastStatementDate", date.toString(Qt::ISODate));
m_lastReconciliationDate = date;
}
void MyMoneyAccount::setParentAccountId(const QString& parent)
{
m_parentAccount = parent;
}
-void MyMoneyAccount::setAccountType(const accountTypeE type)
+void MyMoneyAccount::setAccountType(const Account type)
{
m_accountType = type;
}
void MyMoneyAccount::addAccountId(const QString& account)
{
if (!m_accountList.contains(account))
m_accountList += account;
}
void MyMoneyAccount::removeAccountIds()
{
m_accountList.clear();
}
void MyMoneyAccount::removeAccountId(const QString& account)
{
int pos;
pos = m_accountList.indexOf(account);
if (pos != -1)
m_accountList.removeAt(pos);
}
bool MyMoneyAccount::operator == (const MyMoneyAccount& right) const
{
return (MyMoneyKeyValueContainer::operator==(right) &&
MyMoneyObject::operator==(right) &&
(m_accountList == right.m_accountList) &&
(m_accountType == right.m_accountType) &&
(m_lastModified == right.m_lastModified) &&
(m_lastReconciliationDate == right.m_lastReconciliationDate) &&
((m_name.length() == 0 && right.m_name.length() == 0) || (m_name == right.m_name)) &&
((m_number.length() == 0 && right.m_number.length() == 0) || (m_number == right.m_number)) &&
((m_description.length() == 0 && right.m_description.length() == 0) || (m_description == right.m_description)) &&
(m_openingDate == right.m_openingDate) &&
(m_parentAccount == right.m_parentAccount) &&
(m_currencyId == right.m_currencyId) &&
(m_institution == right.m_institution));
}
-MyMoneyAccount::accountTypeE MyMoneyAccount::accountGroup() const
+Account MyMoneyAccount::accountGroup() const
{
switch (m_accountType) {
- case MyMoneyAccount::Checkings:
- case MyMoneyAccount::Savings:
- case MyMoneyAccount::Cash:
- case MyMoneyAccount::Currency:
- case MyMoneyAccount::Investment:
- case MyMoneyAccount::MoneyMarket:
- case MyMoneyAccount::CertificateDep:
- case MyMoneyAccount::AssetLoan:
- case MyMoneyAccount::Stock:
- return MyMoneyAccount::Asset;
-
- case MyMoneyAccount::CreditCard:
- case MyMoneyAccount::Loan:
- return MyMoneyAccount::Liability;
+ case Account::Checkings:
+ case Account::Savings:
+ case Account::Cash:
+ case Account::Currency:
+ case Account::Investment:
+ case Account::MoneyMarket:
+ case Account::CertificateDep:
+ case Account::AssetLoan:
+ case Account::Stock:
+ return Account::Asset;
+
+ case Account::CreditCard:
+ case Account::Loan:
+ return Account::Liability;
default:
return m_accountType;
}
}
void MyMoneyAccount::setCurrencyId(const QString& id)
{
m_currencyId = id;
}
bool MyMoneyAccount::isAssetLiability() const
{
- return accountGroup() == Asset || accountGroup() == Liability;
+ return accountGroup() == Account::Asset || accountGroup() == Account::Liability;
}
bool MyMoneyAccount::isIncomeExpense() const
{
- return accountGroup() == Income || accountGroup() == Expense;
+ return accountGroup() == Account::Income || accountGroup() == Account::Expense;
}
bool MyMoneyAccount::isLoan() const
{
- return accountType() == Loan || accountType() == AssetLoan;
+ return accountType() == Account::Loan || accountType() == Account::AssetLoan;
}
bool MyMoneyAccount::isInvest() const
{
- return accountType() == Stock;
+ return accountType() == Account::Stock;
}
bool MyMoneyAccount::isLiquidAsset() const
{
- return accountType() == Checkings ||
- accountType() == Savings ||
- accountType() == Cash;
+ return accountType() == Account::Checkings ||
+ accountType() == Account::Savings ||
+ accountType() == Account::Cash;
}
bool MyMoneyAccount::isCostCenterRequired() const
{
return value("CostCenter").toLower() == QLatin1String("yes");
}
void MyMoneyAccount::setCostCenterRequired(bool required)
{
if(required) {
setValue("CostCenter", "yes");
} else {
deletePair("CostCenter");
}
}
template<>
QList< payeeIdentifierTyped< ::payeeIdentifiers::ibanBic> > MyMoneyAccount::payeeIdentifiersByType() const
{
payeeIdentifierTyped<payeeIdentifiers::ibanBic> ident = payeeIdentifierTyped<payeeIdentifiers::ibanBic>(new payeeIdentifiers::ibanBic);
ident->setIban(value(getAttrName(anIBAN)));
if (!institutionId().isEmpty()) {
const MyMoneyInstitution institution = MyMoneyFile::instance()->institution(institutionId());
ident->setBic(institution.value(getAttrName(anBIC)));
}
ident->setOwnerName(MyMoneyFile::instance()->user().name());
QList< payeeIdentifierTyped<payeeIdentifiers::ibanBic> > typedList;
typedList << ident;
return typedList;
}
MyMoneyAccountLoan::MyMoneyAccountLoan(const MyMoneyAccount& acc)
: MyMoneyAccount(acc)
{
}
const MyMoneyMoney MyMoneyAccountLoan::loanAmount() const
{
return MyMoneyMoney(value("loan-amount"));
}
void MyMoneyAccountLoan::setLoanAmount(const MyMoneyMoney& amount)
{
setValue("loan-amount", amount.toString());
}
const MyMoneyMoney MyMoneyAccountLoan::interestRate(const QDate& date) const
{
MyMoneyMoney rate;
QString key;
QString val;
if (!date.isValid())
return rate;
key.sprintf("ir-%04d-%02d-%02d", date.year(), date.month(), date.day());
QRegExp regExp("ir-(\\d{4})-(\\d{2})-(\\d{2})");
QMap<QString, QString>::ConstIterator it;
for (it = pairs().begin(); it != pairs().end(); ++it) {
if (regExp.indexIn(it.key()) > -1) {
if (qstrcmp(it.key().toLatin1(), key.toLatin1()) <= 0)
val = *it;
else
break;
} else if (!val.isEmpty())
break;
}
if (!val.isEmpty()) {
rate = MyMoneyMoney(val);
}
return rate;
}
void MyMoneyAccountLoan::setInterestRate(const QDate& date, const MyMoneyMoney& value)
{
if (!date.isValid())
return;
QString key;
key.sprintf("ir-%04d-%02d-%02d", date.year(), date.month(), date.day());
setValue(key, value.toString());
}
MyMoneyAccountLoan::interestDueE MyMoneyAccountLoan::interestCalculation() const
{
QString payTime(value("interest-calculation"));
if (payTime == "paymentDue")
return paymentDue;
return paymentReceived;
}
void MyMoneyAccountLoan::setInterestCalculation(const MyMoneyAccountLoan::interestDueE onReception)
{
if (onReception == paymentDue)
setValue("interest-calculation", "paymentDue");
else
setValue("interest-calculation", "paymentReceived");
}
const QDate MyMoneyAccountLoan::nextInterestChange() const
{
QDate rc;
QRegExp regExp("(\\d{4})-(\\d{2})-(\\d{2})");
if (regExp.indexIn(value("interest-nextchange")) != -1) {
rc.setDate(regExp.cap(1).toInt(), regExp.cap(2).toInt(), regExp.cap(3).toInt());
}
return rc;
}
void MyMoneyAccountLoan::setNextInterestChange(const QDate& date)
{
setValue("interest-nextchange", date.toString(Qt::ISODate));
}
int MyMoneyAccountLoan::interestChangeFrequency(int* unit) const
{
int rc = -1;
if (unit)
*unit = 1;
QRegExp regExp("(\\d+)/(\\d{1})");
if (regExp.indexIn(value("interest-changefrequency")) != -1) {
rc = regExp.cap(1).toInt();
if (unit != 0) {
*unit = regExp.cap(2).toInt();
}
}
return rc;
}
void MyMoneyAccountLoan::setInterestChangeFrequency(const int amount, const int unit)
{
QString val;
val.sprintf("%d/%d", amount, unit);
setValue("interest-changeFrequency", val);
}
const QString MyMoneyAccountLoan::schedule() const
{
return QString(value("schedule").toLatin1());
}
void MyMoneyAccountLoan::setSchedule(const QString& sched)
{
setValue("schedule", sched);
}
bool MyMoneyAccountLoan::fixedInterestRate() const
{
// make sure, that an empty kvp element returns true
return !(value("fixed-interest") == "no");
}
void MyMoneyAccountLoan::setFixedInterestRate(const bool fixed)
{
setValue("fixed-interest", fixed ? "yes" : "no");
if (fixed) {
deletePair("interest-nextchange");
deletePair("interest-changeFrequency");
}
}
const MyMoneyMoney MyMoneyAccountLoan::finalPayment() const
{
return MyMoneyMoney(value("final-payment"));
}
void MyMoneyAccountLoan::setFinalPayment(const MyMoneyMoney& finalPayment)
{
setValue("final-payment", finalPayment.toString());
}
unsigned int MyMoneyAccountLoan::term() const
{
return value("term").toUInt();
}
void MyMoneyAccountLoan::setTerm(const unsigned int payments)
{
setValue("term", QString::number(payments));
}
const MyMoneyMoney MyMoneyAccountLoan::periodicPayment() const
{
return MyMoneyMoney(value("periodic-payment"));
}
void MyMoneyAccountLoan::setPeriodicPayment(const MyMoneyMoney& payment)
{
setValue("periodic-payment", payment.toString());
}
const QString MyMoneyAccountLoan::payee() const
{
return value("payee");
}
void MyMoneyAccountLoan::setPayee(const QString& payee)
{
setValue("payee", payee);
}
const QString MyMoneyAccountLoan::interestAccountId() const
{
return QString();
}
void MyMoneyAccountLoan::setInterestAccountId(const QString& /* id */)
{
}
bool MyMoneyAccountLoan::hasReferenceTo(const QString& id) const
{
return MyMoneyAccount::hasReferenceTo(id)
|| (id == payee())
|| (id == schedule());
}
void MyMoneyAccountLoan::setInterestCompounding(int frequency)
{
setValue("compoundingFrequency", QString("%1").arg(frequency));
}
int MyMoneyAccountLoan::interestCompounding() const
{
return value("compoundingFrequency").toInt();
}
void MyMoneyAccount::writeXML(QDomDocument& document, QDomElement& parent) const
{
QDomElement el = document.createElement(nodeNames[nnAccount]);
writeBaseXML(document, el);
el.setAttribute(getAttrName(anParentAccount), parentAccountId());
el.setAttribute(getAttrName(anLastReconciled), dateToString(lastReconciliationDate()));
el.setAttribute(getAttrName(anLastModified), dateToString(lastModified()));
el.setAttribute(getAttrName(anInstitution), institutionId());
el.setAttribute(getAttrName(anOpened), dateToString(openingDate()));
el.setAttribute(getAttrName(anNumber), number());
// el.setAttribute(getAttrName(anOpeningBalance), openingBalance().toString());
- el.setAttribute(getAttrName(anType), accountType());
+ el.setAttribute(getAttrName(anType), (int)accountType());
el.setAttribute(getAttrName(anName), name());
el.setAttribute(getAttrName(anDescription), description());
if (!currencyId().isEmpty())
el.setAttribute(getAttrName(anCurrency), currencyId());
//Add in subaccount information, if this account has subaccounts.
if (accountCount()) {
QDomElement subAccounts = document.createElement(getElName(enSubAccounts));
QStringList::ConstIterator it;
for (it = accountList().begin(); it != accountList().end(); ++it) {
QDomElement temp = document.createElement(getElName(enSubAccount));
temp.setAttribute(getAttrName(anID), (*it));
subAccounts.appendChild(temp);
}
el.appendChild(subAccounts);
}
// Write online banking settings
if (m_onlineBankingSettings.pairs().count()) {
QDomElement onlinesettings = document.createElement(getElName(enOnlineBanking));
QMap<QString, QString>::const_iterator it_key = m_onlineBankingSettings.pairs().begin();
while (it_key != m_onlineBankingSettings.pairs().end()) {
onlinesettings.setAttribute(it_key.key(), it_key.value());
++it_key;
}
el.appendChild(onlinesettings);
}
// FIXME drop the lastStatementDate record from the KVP when it is
// not stored there after setLastReconciliationDate() has been changed
// See comment there when this will happen
// deletePair("lastStatementDate");
//Add in Key-Value Pairs for accounts.
MyMoneyKeyValueContainer::writeXML(document, el);
parent.appendChild(el);
}
bool MyMoneyAccount::hasReferenceTo(const QString& id) const
{
return (id == m_institution) || (id == m_parentAccount) || (id == m_currencyId);
}
void MyMoneyAccount::setOnlineBankingSettings(const MyMoneyKeyValueContainer& values)
{
m_onlineBankingSettings = values;
}
const MyMoneyKeyValueContainer& MyMoneyAccount::onlineBankingSettings() const
{
return m_onlineBankingSettings;
}
void MyMoneyAccount::setClosed(bool closed)
{
if (closed)
setValue("mm-closed", "yes");
else
deletePair("mm-closed");
}
bool MyMoneyAccount::isClosed() const
{
return !(value("mm-closed").isEmpty());
}
int MyMoneyAccount::fraction(const MyMoneySecurity& sec) const
{
int fraction;
- if (m_accountType == Cash)
+ if (m_accountType == Account::Cash)
fraction = sec.smallestCashFraction();
else
fraction = sec.smallestAccountFraction();
return fraction;
}
int MyMoneyAccount::fraction(const MyMoneySecurity& sec)
{
- if (m_accountType == Cash)
+ if (m_accountType == Account::Cash)
m_fraction = sec.smallestCashFraction();
else
m_fraction = sec.smallestAccountFraction();
return m_fraction;
}
int MyMoneyAccount::fraction() const
{
return m_fraction;
}
bool MyMoneyAccount::isCategory() const
{
- return m_accountType == Income || m_accountType == Expense;
+ return m_accountType == Account::Income || m_accountType == Account::Expense;
}
QString MyMoneyAccount::brokerageName() const
{
- if (m_accountType == Investment)
+ if (m_accountType == Account::Investment)
return QString("%1 (%2)").arg(m_name, i18nc("Brokerage (suffix for account names)", "Brokerage"));
return m_name;
}
void MyMoneyAccount::adjustBalance(const MyMoneySplit& s, bool reverse)
{
if (s.action() == MyMoneySplit::ActionSplitShares) {
if (reverse)
m_balance = m_balance / s.shares();
else
m_balance = m_balance * s.shares();
} else {
if (reverse)
m_balance -= s.shares();
else
m_balance += s.shares();
}
}
QPixmap MyMoneyAccount::accountPixmap(const bool reconcileFlag, const int size) const
{
- static const QHash<MyMoneyAccount::accountTypeE, Icon> accToIco {
- {MyMoneyAccount::Asset, Icon::ViewAsset},
- {MyMoneyAccount::Investment, Icon::ViewStock},
- {MyMoneyAccount::Stock, Icon::ViewStock},
- {MyMoneyAccount::MoneyMarket, Icon::ViewStock},
- {MyMoneyAccount::Checkings, Icon::ViewChecking},
- {MyMoneyAccount::Savings, Icon::ViewSaving},
- {MyMoneyAccount::AssetLoan, Icon::ViewLoanAsset},
- {MyMoneyAccount::Loan, Icon::ViewLoan},
- {MyMoneyAccount::CreditCard, Icon::ViewCreditCard},
- {MyMoneyAccount::Asset, Icon::ViewAsset},
- {MyMoneyAccount::Cash, Icon::ViewCash},
- {MyMoneyAccount::Income, Icon::ViewIncome},
- {MyMoneyAccount::Expense, Icon::ViewExpense},
- {MyMoneyAccount::Equity, Icon::ViewEquity}
+ static const QHash<Account, Icon> accToIco {
+ {Account::Asset, Icon::ViewAsset},
+ {Account::Investment, Icon::ViewStock},
+ {Account::Stock, Icon::ViewStock},
+ {Account::MoneyMarket, Icon::ViewStock},
+ {Account::Checkings, Icon::ViewChecking},
+ {Account::Savings, Icon::ViewSaving},
+ {Account::AssetLoan, Icon::ViewLoanAsset},
+ {Account::Loan, Icon::ViewLoan},
+ {Account::CreditCard, Icon::ViewCreditCard},
+ {Account::Asset, Icon::ViewAsset},
+ {Account::Cash, Icon::ViewCash},
+ {Account::Income, Icon::ViewIncome},
+ {Account::Expense, Icon::ViewExpense},
+ {Account::Equity, Icon::ViewEquity}
};
Icon ixIcon = accToIco.value(accountType(), Icon::ViewLiability);
QString kyIcon = g_Icons.value(ixIcon) + QString::number(size);
QPixmap pxIcon;
if (!QPixmapCache::find(kyIcon, pxIcon)) {
pxIcon = QIcon::fromTheme(g_Icons.value(ixIcon)).pixmap(size); // Qt::AA_UseHighDpiPixmaps (in Qt 5.7) doesn't return highdpi pixmap
QPixmapCache::insert(kyIcon, pxIcon);
}
if (isClosed())
ixIcon = Icon::AccountClosed;
else if (reconcileFlag)
ixIcon = Icon::FlagGreen;
else if (!onlineBankingSettings().value("provider").isEmpty())
ixIcon = Icon::Download;
else
return pxIcon;
QPixmap pxOverlay = QIcon::fromTheme(g_Icons[ixIcon]).pixmap(size);
QPainter pxPainter(&pxIcon);
const QSize szIcon = pxIcon.size();
pxPainter.drawPixmap(szIcon.width() / 2, szIcon.height() / 2,
szIcon.width() / 2, szIcon.height() / 2, pxOverlay);
return pxIcon;
}
-QString MyMoneyAccount::accountTypeToString(const MyMoneyAccount::accountTypeE accountType)
+QString MyMoneyAccount::accountTypeToString(const Account accountType)
{
QString returnString;
switch (accountType) {
- case MyMoneyAccount::Checkings:
+ case Account::Checkings:
returnString = i18n("Checking");
break;
- case MyMoneyAccount::Savings:
+ case Account::Savings:
returnString = i18n("Savings");
break;
- case MyMoneyAccount::CreditCard:
+ case Account::CreditCard:
returnString = i18n("Credit Card");
break;
- case MyMoneyAccount::Cash:
+ case Account::Cash:
returnString = i18n("Cash");
break;
- case MyMoneyAccount::Loan:
+ case Account::Loan:
returnString = i18n("Loan");
break;
- case MyMoneyAccount::CertificateDep:
+ case Account::CertificateDep:
returnString = i18n("Certificate of Deposit");
break;
- case MyMoneyAccount::Investment:
+ case Account::Investment:
returnString = i18n("Investment");
break;
- case MyMoneyAccount::MoneyMarket:
+ case Account::MoneyMarket:
returnString = i18n("Money Market");
break;
- case MyMoneyAccount::Asset:
+ case Account::Asset:
returnString = i18n("Asset");
break;
- case MyMoneyAccount::Liability:
+ case Account::Liability:
returnString = i18n("Liability");
break;
- case MyMoneyAccount::Currency:
+ case Account::Currency:
returnString = i18n("Currency");
break;
- case MyMoneyAccount::Income:
+ case Account::Income:
returnString = i18n("Income");
break;
- case MyMoneyAccount::Expense:
+ case Account::Expense:
returnString = i18n("Expense");
break;
- case MyMoneyAccount::AssetLoan:
+ case Account::AssetLoan:
returnString = i18n("Investment Loan");
break;
- case MyMoneyAccount::Stock:
+ case Account::Stock:
returnString = i18n("Stock");
break;
- case MyMoneyAccount::Equity:
+ case Account::Equity:
returnString = i18n("Equity");
break;
default:
returnString = i18nc("Unknown account type", "Unknown");
}
return returnString;
}
bool MyMoneyAccount::addReconciliation(const QDate& date, const MyMoneyMoney& amount)
{
m_reconciliationHistory[date] = amount;
QString history, sep;
QMap<QDate, MyMoneyMoney>::const_iterator it;
for (it = m_reconciliationHistory.constBegin();
it != m_reconciliationHistory.constEnd();
++it) {
history += QString("%1%2:%3").arg(sep,
it.key().toString(Qt::ISODate),
(*it).toString());
sep = QLatin1Char(';');
}
setValue("reconciliationHistory", history);
return true;
}
const QMap<QDate, MyMoneyMoney>& MyMoneyAccount::reconciliationHistory()
{
// check if the internal history member is already loaded
if (m_reconciliationHistory.count() == 0
&& !value("reconciliationHistory").isEmpty()) {
QStringList entries = value("reconciliationHistory").split(';');
foreach (const QString& entry, entries) {
QStringList parts = entry.split(':');
QDate date = QDate::fromString(parts[0], Qt::ISODate);
MyMoneyMoney amount(parts[1]);
if (parts.count() == 2 && date.isValid()) {
m_reconciliationHistory[date] = amount;
}
}
}
return m_reconciliationHistory;
}
/**
* @todo Improve setting of country for nationalAccount
*/
QList< payeeIdentifier > MyMoneyAccount::payeeIdentifiers() const
{
QList< payeeIdentifier > list;
MyMoneyFile* file = MyMoneyFile::instance();
// Iban & Bic
if (!value(getAttrName(anIBAN)).isEmpty()) {
payeeIdentifierTyped<payeeIdentifiers::ibanBic> iban(new payeeIdentifiers::ibanBic);
iban->setIban(value(getAttrName(anIBAN)));
iban->setBic(file->institution(institutionId()).value(getAttrName(anBIC)));
iban->setOwnerName(file->user().name());
list.append(iban);
}
// National Account number
if (!number().isEmpty()) {
payeeIdentifierTyped<payeeIdentifiers::nationalAccount> national(new payeeIdentifiers::nationalAccount);
national->setAccountNumber(number());
national->setBankCode(file->institution(institutionId()).sortcode());
if (file->user().state().length() == 2)
national->setCountry(file->user().state());
national->setOwnerName(file->user().name());
list.append(national);
}
return list;
}
const QString MyMoneyAccount::getElName(const elNameE _el)
{
static const QMap<elNameE, QString> elNames = {
{enSubAccount, QStringLiteral("SUBACCOUNT")},
{enSubAccounts, QStringLiteral("SUBACCOUNTS")},
{enOnlineBanking, QStringLiteral("ONLINEBANKING")}
};
return elNames[_el];
}
const QString MyMoneyAccount::getAttrName(const attrNameE _attr)
{
static const QHash<attrNameE, QString> attrNames = {
{anID, QStringLiteral("id")},
{anName, QStringLiteral("name")},
{anType, QStringLiteral("type")},
{anParentAccount, QStringLiteral("parentaccount")},
{anLastReconciled, QStringLiteral("lastreconciled")},
{anLastModified, QStringLiteral("lastmodified")},
{anInstitution, QStringLiteral("institution")},
{anOpened, QStringLiteral("opened")},
{anNumber, QStringLiteral("number")},
{anType, QStringLiteral("type")},
{anDescription, QStringLiteral("description")},
{anCurrency, QStringLiteral("currency")},
{anOpeningBalance, QStringLiteral("openingbalance")},
{anIBAN, QStringLiteral("iban")},
{anBIC, QStringLiteral("bic")},
};
return attrNames[_attr];
}
diff --git a/kmymoney/mymoney/mymoneyaccount.h b/kmymoney/mymoney/mymoneyaccount.h
index 787393854..73affe54e 100644
--- a/kmymoney/mymoney/mymoneyaccount.h
+++ b/kmymoney/mymoney/mymoneyaccount.h
@@ -1,803 +1,777 @@
/***************************************************************************
mymoneyaccount.h
-------------------
copyright : (C) 2002 by Thomas Baumgart <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 MYMONEYACCOUNT_H
#define MYMONEYACCOUNT_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QString>
#include <QStringList>
-#include <QPixmap>
#include <QDate>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneykeyvaluecontainer.h"
#include "mymoneymoney.h"
#include "mymoneyobject.h"
-#include "mymoneysecurity.h"
#include "kmm_mymoney_export.h"
#include "mymoneyunittestable.h"
+#include "mymoneyenums.h"
+class QPixmap;
+class MyMoneySecurity;
class MyMoneySplit;
class payeeIdentifier;
namespace payeeIdentifiers { class ibanBic; }
template <class T> class payeeIdentifierTyped;
/**
* A representation of an account.
* This object represents any type of account, those held at an
* institution as well as the accounts used for double entry
* accounting.
*
* Currently, the following account types are known:
*
- * @li UnknownAccountType
+ * @li Unknown
* @li Checkings
* @li Savings
* @li Cash
* @li CreditCard
* @li Loan (collected)
* @li CertificateDep
* @li Investment
* @li MoneyMarket
* @li Currency
* @li Asset
* @li Liability
* @li Income
* @li Expense
* @li Loan (given)
* @li Stock
* @li Equity
*
* @see MyMoneyInstitution
* @see MyMoneyFile
*
* @author Michael Edwardes 2000-2001
* @author Thomas Baumgart 2002
*
**/
class KMM_MYMONEY_EXPORT MyMoneyAccount : public MyMoneyObject, public MyMoneyKeyValueContainer /*, public MyMoneyPayeeIdentifierContainer */
{
friend class MyMoneyObjectContainer;
Q_GADGET
KMM_MYMONEY_UNIT_TESTABLE
public:
enum elNameE { enSubAccount, enSubAccounts, enOnlineBanking };
Q_ENUM(elNameE)
enum attrNameE { anID, anName, anType, anParentAccount, anLastReconciled,
anLastModified, anInstitution, anOpened, anNumber,
anDescription, anCurrency, anOpeningBalance,
anIBAN, anBIC,
};
Q_ENUM(attrNameE)
- /**
- * Account types currently supported.
- */
- typedef enum _accountTypeE {
- UnknownAccountType = 0, /**< For error handling */
- Checkings, /**< Standard checking account */
- Savings, /**< Typical savings account */
- Cash, /**< Denotes a shoe-box or pillowcase stuffed
- with cash */
- CreditCard, /**< Credit card accounts */
- Loan, /**< Loan and mortgage accounts (liability) */
- CertificateDep, /**< Certificates of Deposit */
- Investment, /**< Investment account */
- MoneyMarket, /**< Money Market Account */
- Asset, /**< Denotes a generic asset account.*/
- Liability, /**< Denotes a generic liability account.*/
- Currency, /**< Denotes a currency trading account. */
- Income, /**< Denotes an income account */
- Expense, /**< Denotes an expense account */
- AssetLoan, /**< Denotes a loan (asset of the owner of this object) */
- Stock, /**< Denotes an security account as sub-account for an investment */
- Equity, /**< Denotes an equity account e.g. opening/closeing balance */
-
- /* insert new account types above this line */
- MaxAccountTypes /**< Denotes the number of different account types */
- } accountTypeE;
-
/**
* This is the constructor for a new empty account
*/
MyMoneyAccount();
/**
* This is the constructor for a new account known to the current file
* This is the only constructor that will set the attribute m_openingDate
* to a correct value.
*
* @param id id assigned to the account
* @param right account definition
*/
MyMoneyAccount(const QString& id, const MyMoneyAccount& right);
/**
* This is the constructor for an account that is described by a
* QDomElement (e.g. from a file).
*
* @param el const reference to the QDomElement from which to
* create the object
*/
MyMoneyAccount(const QDomElement& el);
/**
* This is the destructor for any MyMoneyAccount object
*/
~MyMoneyAccount();
/**
* This operator tests for equality of two MyMoneyAccount objects
*/
bool operator == (const MyMoneyAccount &) const;
/**
* This converts the account type into one of the four
* major account types liability, asset, expense or income.
*
* The current assignment is as follows:
*
* - Asset
* - Asset
* - Checkings
* - Savings
* - Cash
* - Currency
* - Investment
* - MoneyMarket
* - CertificateDep
* - AssetLoan
* - Stock
*
* - Liability
* - Liability
* - CreditCard
* - Loan
*
* - Income
* - Income
*
* - Expense
* - Expense
*
* @return accountTypeE of major account type
*/
- MyMoneyAccount::accountTypeE accountGroup() const;
+ eMyMoney::Account accountGroup() const;
/**
* This method returns the id of the MyMoneyInstitution object this account
* belongs to.
* @return id of MyMoneyInstitution object. QString() if it is
* an internal account
* @see setInstitution
*/
const QString& institutionId() const {
return m_institution;
}
/**
* This method returns the name of the account
* @return name of account
* @see setName()
*/
const QString& name() const {
return m_name;
}
/**
* This method returns the number of the account at the institution
* @return number of account at the institution
* @see setNumber
*/
const QString& number() const {
return m_number;
}
/**
* This method returns the descriptive text of the account.
* @return description of account
* @see setDescription
*/
const QString& description() const {
return m_description;
}
/**
* This method returns the opening date of this account
* @return date of opening of this account as const QDate value
* @see setOpeningDate()
*/
const QDate& openingDate() const {
return m_openingDate;
}
/**
* This method returns the date of the last reconciliation of this account
* @return date of last reconciliation as const QDate value
* @see setLastReconciliationDate
*/
const QDate& lastReconciliationDate() const {
return m_lastReconciliationDate;
}
/**
* This method returns the date the account was last modified
* @return date of last modification as const QDate value
* @see setLastModified
*/
const QDate& lastModified() const {
return m_lastModified;
}
/**
* This method is used to return the ID of the parent account
* @return QString with the ID of the parent of this account
*/
const QString& parentAccountId() const {
return m_parentAccount;
};
/**
* This method returns the list of the account id's of
* subordinate accounts
* @return QStringList account ids
*/
const QStringList& accountList() const {
return m_accountList;
};
/**
* This method returns the number of entries in the m_accountList
* @return number of entries in the accountList
*/
int accountCount() const {
return m_accountList.count();
};
/**
* This method is used to add an account id as sub-ordinate account
* @param account const QString reference to account ID
*/
void addAccountId(const QString& account);
/**
* This method is used to remove an account from the list of
* sub-ordinate accounts.
* @param account const QString reference to account ID to be removed
*/
void removeAccountId(const QString& account);
/**
* This method is used to remove all accounts from the list of
* sub-ordinate accounts.
*/
void removeAccountIds();
/**
* This method is used to modify the date of the last
* modification access.
* @param date date of last modification
* @see lastModified
*/
void setLastModified(const QDate& date);
/**
* This method is used to set the name of the account
* @param name name of the account
* @see name
*/
void setName(const QString& name);
/**
* This method is used to set the number of the account at the institution
* @param number number of the account
* @see number
*/
void setNumber(const QString& number);
/**
* This method allows to control if a cost center assignment is required
* for this account. It is if @a required is @c true (the default).
*/
void setCostCenterRequired(bool required = true);
/**
* Return the stored account identifiers
*
* @internal This method is temporary until MyMoneyAccount is a MyMoneyPayeeIdentifierContainer. But before this
* can happen the account numbers and iban kvp data must be moved there.
*/
QList< payeeIdentifier > payeeIdentifiers() const;
/**
* @see MyMoneyPayeeIdentifierContainer::payeeIdentifiersByType()
*/
template< class type >
QList< ::payeeIdentifierTyped<type> > payeeIdentifiersByType() const;
/**
* This method is used to set the descriptive text of the account
*
* @param desc text that serves as description
* @see setDescription
*/
void setDescription(const QString& desc);
/**
* This method is used to set the id of the institution this account
* belongs to.
*
* @param id id of the institution this account belongs to
* @see institution
*/
void setInstitutionId(const QString& id);
/**
* This method is used to set the opening date information of an
* account.
*
* @param date QDate of opening date
* @see openingDate
*/
void setOpeningDate(const QDate& date);
/**
* This method is used to set the date of the last reconciliation
* of an account.
* @param date QDate of last reconciliation
* @see lastReconciliationDate
*/
void setLastReconciliationDate(const QDate& date);
/**
* This method is used to change the account type
*
* @param type account type
*/
- void setAccountType(const accountTypeE type);
+ void setAccountType(const eMyMoney::Account type);
/**
* This method is used to set a new parent account id
* @param parent QString reference to new parent account
*/
void setParentAccountId(const QString& parent);
/**
* This method is used to update m_lastModified to the current date
*/
void touch() {
setLastModified(QDate::currentDate());
}
/**
* This method returns the type of the account.
*/
- accountTypeE accountType() const {
+ eMyMoney::Account accountType() const {
return m_accountType;
}
/**
* This method retrieves the id of the currency used with this account.
* If the return value is empty, the base currency should be used.
*
* @return id of currency
*/
const QString& currencyId() const {
return m_currencyId;
};
/**
* This method sets the id of the currency used with this account.
*
* @param id ID of currency to be associated with this account.
*/
void setCurrencyId(const QString& id);
void writeXML(QDomDocument& document, QDomElement& parent) const;
/**
* This method checks if a reference to the given object exists. It returns,
* a @p true if the object is referencing the one requested by the
* parameter @p id. If it does not, this method returns @p false.
*
* @param id id of the object to be checked for references
* @retval true This object references object with id @p id.
* @retval false This object does not reference the object with id @p id.
*/
virtual bool hasReferenceTo(const QString& id) const;
/**
* This member returns the balance of this account based on
* all transactions stored in the journal.
*/
const MyMoneyMoney& balance() const {
return m_balance;
}
/**
* This method adjusts the balance of this account
* according to the difference contained in the split @p s.
* If the s.action() is MyMoneySplit::ActionSplitShares then
* the balance will be adjusted accordingly.
*
* @param s const reference to MyMoneySplit object containing the
* value to be added/subtracted to/from the balance
* @param reverse add (false) or subtract (true) the shares contained in the split.
* It also affects the balance for share splits in the opposite direction.
*/
void adjustBalance(const MyMoneySplit& s, bool reverse = false);
/**
* This method sets the balance of this account
* according to the value provided by @p val.
*
* @param val const reference to MyMoneyMoney object containing the
* value to be assigned to the balance
*/
void setBalance(const MyMoneyMoney& val) {
m_balance = val;
}
/**
* This method sets the kvp's for online banking with this account
*
* @param values The container of kvp's needed when connecting to this account
*/
void setOnlineBankingSettings(const MyMoneyKeyValueContainer& values);
/**
* This method retrieves the kvp's for online banking with this account
*
* @return The container of kvp's needed when connecting to this account
*/
const MyMoneyKeyValueContainer& onlineBankingSettings() const;
/**
* This method sets the closed flag for the account. This is just
* an informational flag for the application. It has no other influence
* on the behaviour of the account object. The default for
* new objects @p open.
*
* @param isClosed mark the account closed (@p true) or open (@p false).
*/
void setClosed(bool isClosed);
/**
* Return the closed flag for the account.
*
* @retval false account is marked open (the default for new accounts)
* @retval true account is marked closed
*/
bool isClosed() const;
/**
* returns the applicable smallest fraction for this account
* for the given security based on the account type. At the same
* time, m_fraction is updated to the value returned.
*
* @param sec const reference to currency (security)
*
* @retval sec.smallestCashFraction() for account type Cash
* @retval sec.smallestAccountFraction() for all other account types
*/
int fraction(const MyMoneySecurity& sec);
/**
* Same as the above method, but does not modify m_fraction.
*/
int fraction(const MyMoneySecurity& sec) const;
/**
* This method returns the stored value for the fraction of this
* account or -1 if not initialized. It can be initialized by
* calling fraction(const MyMoneySecurity& sec) once.
*
* @note Don't use this method outside of KMyMoney application context (eg. testcases).
* Use the above method instead.
*/
int fraction() const;
/**
* This method returns @a true if the account type is
* either Income or Expense
*
* @retval true account is of type income or expense
* @retval false for all other account types
*
* @deprecated use isIncomeExpense() instead
*/
KMM_MYMONEY_DEPRECATED bool isCategory() const;
/**
* This method returns @a true if the account type is
* either Income or Expense
*
* @retval true account is of type income or expense
* @retval false for all other account types
*/
bool isIncomeExpense() const;
/**
* This method returns @a true if the account type is
* either Asset or Liability
*
* @retval true account is of type asset or liability
* @retval false for all other account types
*/
bool isAssetLiability() const;
/**
* This method returns @a true if the account type is
* either AssetLoan or Loan
*
* @retval true account is of type Loan or AssetLoan
* @retval false for all other account types
*/
bool isLoan() const;
/**
* This method returns @a true if the account type is
* Stock
*
* @retval true account is of type Stock
* @retval false for all other account types
*/
bool isInvest() const;
/**
* This method returns @a true if the account type is
* Checkings, Savings or Cash
*
* @retval true account is of type Checkings, Savings or Cash
* @retval false for all other account types
*/
bool isLiquidAsset() const;
/**
* This method returns true if a costcenter assignment is required for this account
*/
bool isCostCenterRequired() const;
/**
* This method returns a name that has a brokerage suffix of
* the current name. It only works on investment accounts and
* returns the name for all other cases.
*/
QString brokerageName() const;
/**
* @param reconcileFlag if set to @a true a reconcile overlay will be
* added to the pixmap returned
* @param size is a hint for the size of the icon
* @return a pixmap using DesktopIcon for the account type
*/
QPixmap accountPixmap(const bool reconcileFlag = false, const int size = 64) const;
/**
* This method is used to convert the internal representation of
* an account type into a human readable format
*
* @param accountType numerical representation of the account type.
- * For possible values, see MyMoneyAccount::accountTypeE
+ * For possible values, see eMyMoney::Account
* @return QString representing the human readable form
*/
- static QString accountTypeToString(const MyMoneyAccount::accountTypeE accountType);
+ static QString accountTypeToString(const eMyMoney::Account accountType);
/**
* keeps a history record of a reconciliation for this account on @a date
* with @a amount.
*
* @return @p true in case entry was added, @p false otherwise
*
* @sa reconciliationHistory()
*/
bool addReconciliation(const QDate& date, const MyMoneyMoney& amount);
/**
* @return QMap with the reconciliation history for the account
*
* @sa addReconciliation()
*/
const QMap<QDate, MyMoneyMoney>& reconciliationHistory();
QDataStream &operator<<(const MyMoneyAccount &);
QDataStream &operator>>(MyMoneyAccount &);
private:
/**
* This member variable identifies the type of account
*/
- accountTypeE m_accountType;
+ eMyMoney::Account m_accountType;
/**
* This member variable keeps the ID of the MyMoneyInstitution object
* that this object belongs to.
*/
QString m_institution;
/**
* This member variable keeps the name of the account
* It is solely for documentation purposes and it's contents is not
* used otherwise by the mymoney-engine.
*/
QString m_name;
/**
* This member variable keeps the account number at the institution
* It is solely for documentation purposes and it's contents is not
* used otherwise by the mymoney-engine.
*/
QString m_number;
/**
* This member variable is a description of the account.
* It is solely for documentation purposes and it's contents is not
* used otherwise by the mymoney-engine.
*/
QString m_description;
/**
* This member variable keeps the date when the account
* was last modified.
*/
QDate m_lastModified;
/**
* This member variable keeps the date when the
* account was created as an object in a MyMoneyFile
*/
QDate m_openingDate;
/**
* This member variable keeps the date of the last
* reconciliation of this account
*/
QDate m_lastReconciliationDate;
/**
* This member holds the ID's of all sub-ordinate accounts
*/
QStringList m_accountList;
/**
* This member contains the ID of the parent account
*/
QString m_parentAccount;
/**
* This member contains the ID of the currency associated with this account
*/
QString m_currencyId;
/**
* This member holds the balance of all transactions stored in the journal
* for this account.
*/
MyMoneyMoney m_balance;
/**
* This member variable keeps the set of kvp's needed to establish
* online banking sessions to this account.
*/
MyMoneyKeyValueContainer m_onlineBankingSettings;
/**
* This member keeps the fraction for the account. It is filled by MyMoneyFile
* when set to -1. See also @sa fraction(const MyMoneySecurity&).
*/
int m_fraction;
/**
* This member keeps the reconciliation history
*/
QMap<QDate, MyMoneyMoney> m_reconciliationHistory;
static const QString getElName(const elNameE _el);
static const QString getAttrName(const attrNameE _attr);
};
/**
* This class is a convenience class to access data for loan accounts.
* It does contain the same member variables as a MyMoneyAccount object,
* but serves a set of getter/setter methods to ease the access to
* laon relevant data stored in the key value container of the MyMoneyAccount
* object.
*/
class KMM_MYMONEY_EXPORT MyMoneyAccountLoan : public MyMoneyAccount
{
public:
enum interestDueE {
paymentDue = 0,
paymentReceived
};
enum interestChangeUnitE {
changeDaily = 0,
changeWeekly,
changeMonthly,
changeYearly
};
MyMoneyAccountLoan() {}
MyMoneyAccountLoan(const MyMoneyAccount&);
~MyMoneyAccountLoan() {}
const MyMoneyMoney loanAmount() const;
void setLoanAmount(const MyMoneyMoney& amount);
const MyMoneyMoney interestRate(const QDate& date) const;
void setInterestRate(const QDate& date, const MyMoneyMoney& rate);
interestDueE interestCalculation() const;
void setInterestCalculation(const interestDueE onReception);
const QDate nextInterestChange() const;
void setNextInterestChange(const QDate& date);
const QString schedule() const;
void setSchedule(const QString& sched);
bool fixedInterestRate() const;
void setFixedInterestRate(const bool fixed);
const MyMoneyMoney finalPayment() const;
void setFinalPayment(const MyMoneyMoney& finalPayment);
unsigned int term() const;
void setTerm(const unsigned int payments);
int interestChangeFrequency(int* unit = 0) const;
void setInterestChangeFrequency(const int amount, const int unit);
const MyMoneyMoney periodicPayment() const;
void setPeriodicPayment(const MyMoneyMoney& payment);
int interestCompounding() const;
void setInterestCompounding(int frequency);
const QString payee() const;
void setPayee(const QString& payee);
const QString interestAccountId() const;
void setInterestAccountId(const QString& id);
/**
* This method checks if a reference to the given object exists. It returns,
* a @p true if the object is referencing the one requested by the
* parameter @p id. If it does not, this method returns @p false.
*
* @param id id of the object to be checked for references
* @retval true This object references object with id @p id.
* @retval false This object does not reference the object with id @p id.
*/
virtual bool hasReferenceTo(const QString& id) const;
};
template< class type >
QList< payeeIdentifierTyped<type> > MyMoneyAccount::payeeIdentifiersByType() const
{
QList< payeeIdentifierTyped<type> > typedList;
return typedList;
}
template<>
QList< payeeIdentifierTyped< ::payeeIdentifiers::ibanBic> > MyMoneyAccount::payeeIdentifiersByType() const;
/**
* Make it possible to hold @ref MyMoneyAccount objects,
* @ref accountTypeE and @ref amountTypeE inside @ref QVariant objects.
*/
Q_DECLARE_METATYPE(MyMoneyAccount)
-Q_DECLARE_METATYPE(MyMoneyAccount::accountTypeE)
+Q_DECLARE_METATYPE(eMyMoney::Account)
#endif
diff --git a/kmymoney/mymoney/mymoneyenums.h b/kmymoney/mymoney/mymoneyenums.h
index ed7844ae3..a6c81a72b 100644
--- a/kmymoney/mymoney/mymoneyenums.h
+++ b/kmymoney/mymoney/mymoneyenums.h
@@ -1,33 +1,226 @@
/***************************************************************************
mymoneyenums.h
-------------------
copyright : (C) 2017 by Łukasz Wojniłowicz <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 <QHash>
#ifndef MYMONEYENUMS_H
#define MYMONEYENUMS_H
namespace eMyMoney {
- enum class Security {
- Stock,
- MutualFund,
- Bond,
- Currency,
- None
+ /**
+ * Account types currently supported.
+ */
+ enum class Account {
+ Unknown = 0, /**< For error handling */
+ Checkings, /**< Standard checking account */
+ Savings, /**< Typical savings account */
+ Cash, /**< Denotes a shoe-box or pillowcase stuffed
+ with cash */
+ CreditCard, /**< Credit card accounts */
+ Loan, /**< Loan and mortgage accounts (liability) */
+ CertificateDep, /**< Certificates of Deposit */
+ Investment, /**< Investment account */
+ MoneyMarket, /**< Money Market Account */
+ Asset, /**< Denotes a generic asset account.*/
+ Liability, /**< Denotes a generic liability account.*/
+ Currency, /**< Denotes a currency trading account. */
+ Income, /**< Denotes an income account */
+ Expense, /**< Denotes an expense account */
+ AssetLoan, /**< Denotes a loan (asset of the owner of this object) */
+ Stock, /**< Denotes an security account as sub-account for an investment */
+ Equity, /**< Denotes an equity account e.g. opening/closeing balance */
+
+ /* insert new account types above this line */
+ MaxAccountTypes /**< Denotes the number of different account types */
+ };
+
+ inline uint qHash(const Account key, uint seed) { return ::qHash(static_cast<uint>(key), seed); }
+
+ enum class Security {
+ Stock,
+ MutualFund,
+ Bond,
+ Currency,
+ None
+ };
+
+ inline uint qHash(const Security key, uint seed) { return ::qHash(static_cast<uint>(key), seed); }
+
+ namespace Schedule {
+ /**
+ * This enum is used to describe all the possible schedule frequencies.
+ * The special entry, Any, is used to combine all the other types.
+ */
+ enum class Occurrence {
+ Any = 0,
+ Once = 1,
+ Daily = 2,
+ Weekly = 4,
+ Fortnightly = 8,
+ EveryOtherWeek = 16,
+ EveryHalfMonth = 18,
+ EveryThreeWeeks = 20,
+ EveryThirtyDays = 30,
+ Monthly = 32,
+ EveryFourWeeks = 64,
+ EveryEightWeeks = 126,
+ EveryOtherMonth = 128,
+ EveryThreeMonths = 256,
+ TwiceYearly = 1024,
+ EveryOtherYear = 2048,
+ Quarterly = 4096,
+ EveryFourMonths = 8192,
+ Yearly = 16384
+ };
+
+ /**
+ * This enum is used to describe the schedule type.
+ */
+ enum class Type {
+ Any = 0,
+ Bill = 1,
+ Deposit = 2,
+ Transfer = 4,
+ LoanPayment = 5
+ };
+
+ /**
+ * This enum is used to describe the schedule's payment type.
+ */
+ enum class PaymentType {
+ Any = 0,
+ DirectDebit = 1,
+ DirectDeposit = 2,
+ ManualDeposit = 4,
+ Other = 8,
+ WriteChecque = 16,
+ StandingOrder = 32,
+ BankTransfer = 64
+ };
+
+ /**
+ * This enum is used by the auto-commit functionality.
+ *
+ * Depending upon the value of m_weekendOption the schedule can
+ * be entered on a different date
+ **/
+ enum class WeekendOption {
+ MoveBefore = 0,
+ MoveAfter = 1,
+ MoveNothing = 2
+ };
+ }
+
+ namespace TransactionFilter {
+ // Make sure to keep the following enum valus in sync with the values
+ // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
+ enum class Type {
+ All = 0,
+ Payments,
+ Deposits,
+ Transfers,
+ // insert new constants above of this line
+ LastType
+ };
+
+ // Make sure to keep the following enum valus in sync with the values
+ // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
+ enum class State {
+ All = 0,
+ NotReconciled,
+ Cleared,
+ Reconciled,
+ Frozen,
+ // insert new constants above of this line
+ LastState
+ };
+
+ // Make sure to keep the following enum valus in sync with the values
+ // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
+ enum class Validity {
+ Any = 0,
+ Valid,
+ Invalid,
+ // insert new constants above of this line
+ LastValidity
+ };
+
+ // Make sure to keep the following enum valus in sync with the values
+ // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
+ enum class Date {
+ All = 0,
+ AsOfToday,
+ CurrentMonth,
+ CurrentYear,
+ MonthToDate,
+ YearToDate,
+ YearToMonth,
+ LastMonth,
+ LastYear,
+ Last7Days,
+ Last30Days,
+ Last3Months,
+ Last6Months,
+ Last12Months,
+ Next7Days,
+ Next30Days,
+ Next3Months,
+ Next6Months,
+ Next12Months,
+ UserDefined,
+ Last3ToNext3Months,
+ Last11Months,
+ CurrentQuarter,
+ LastQuarter,
+ NextQuarter,
+ CurrentFiscalYear,
+ LastFiscalYear,
+ Today,
+ Next18Months,
+ // insert new constants above of this line
+ LastDateItem
+ };
+ }
+
+ namespace File {
+ /**
+ * notificationObject identifies the type of the object
+ * for which this notification is stored
+ */
+ enum class Object {
+ Account = 1,
+ Institution,
+ Payee,
+ Transaction,
+ Tag,
+ Schedule,
+ Security,
+ OnlineJob
+ };
+
+ /**
+ * notificationMode identifies the type of notifiation
+ * (add, modify, remove)
+ */
+ enum class Mode {
+ Add = 1,
+ Modify,
+ Remove
};
- inline uint qHash(const Security key, uint seed) { return ::qHash(static_cast<uint>(key), seed); }
+ }
}
#endif
diff --git a/kmymoney/mymoney/mymoneyfile.cpp b/kmymoney/mymoney/mymoneyfile.cpp
index 551c81a44..0ba54d49e 100644
--- a/kmymoney/mymoney/mymoneyfile.cpp
+++ b/kmymoney/mymoney/mymoneyfile.cpp
@@ -1,3683 +1,3722 @@
/***************************************************************************
mymoneyfile.cpp
-------------------
copyright : (C) 2000 by Michael Edwardes <mte@users.sourceforge.net>
(C) 2002, 2007-2011 by Thomas Baumgart <ipwizard@users.sourceforge.net>
+ (C) 2017 by Łukasz Wojniłowicz <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 "mymoneyfile.h"
#include <utility>
// ----------------------------------------------------------------------------
// QT Includes
#include <QString>
#include <QList>
#include <QUuid>
#include <QLocale>
#include <QBitArray>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "imymoneystorage.h"
#include "mymoneyaccount.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 "storageenums.h"
// include the following line to get a 'cout' for debug purposes
// #include <iostream>
+using namespace eMyMoney;
+
const QString MyMoneyFile::AccountSeperator = QChar(':');
MyMoneyFile MyMoneyFile::file;
typedef QList<std::pair<QString, QDate> > BalanceNotifyList;
typedef QMap<QString, bool> CacheNotifyList;
/// @todo make this template based
class MyMoneyNotification
{
public:
- MyMoneyNotification(MyMoneyFile::notificationModeT mode, const MyMoneyTransaction& t) :
- m_objType(MyMoneyFile::notifyTransaction),
+ MyMoneyNotification(File::Mode mode, const MyMoneyTransaction& t) :
+ m_objType(File::Object::Transaction),
m_notificationMode(mode),
m_id(t.id()) {
}
- MyMoneyNotification(MyMoneyFile::notificationModeT mode, const MyMoneyAccount& acc) :
- m_objType(MyMoneyFile::notifyAccount),
+ MyMoneyNotification(File::Mode mode, const MyMoneyAccount& acc) :
+ m_objType(File::Object::Account),
m_notificationMode(mode),
m_id(acc.id()) {
}
- MyMoneyNotification(MyMoneyFile::notificationModeT mode, const MyMoneyInstitution& institution) :
- m_objType(MyMoneyFile::notifyInstitution),
+ MyMoneyNotification(File::Mode mode, const MyMoneyInstitution& institution) :
+ m_objType(File::Object::Institution),
m_notificationMode(mode),
m_id(institution.id()) {
}
- MyMoneyNotification(MyMoneyFile::notificationModeT mode, const MyMoneyPayee& payee) :
- m_objType(MyMoneyFile::notifyPayee),
+ MyMoneyNotification(File::Mode mode, const MyMoneyPayee& payee) :
+ m_objType(File::Object::Payee),
m_notificationMode(mode),
m_id(payee.id()) {
}
- MyMoneyNotification(MyMoneyFile::notificationModeT mode, const MyMoneyTag& tag) :
- m_objType(MyMoneyFile::notifyTag),
+ MyMoneyNotification(File::Mode mode, const MyMoneyTag& tag) :
+ m_objType(File::Object::Tag),
m_notificationMode(mode),
m_id(tag.id()) {
}
- MyMoneyNotification(MyMoneyFile::notificationModeT mode, const MyMoneySchedule& schedule) :
- m_objType(MyMoneyFile::notifySchedule),
+ MyMoneyNotification(File::Mode mode, const MyMoneySchedule& schedule) :
+ m_objType(File::Object::Schedule),
m_notificationMode(mode),
m_id(schedule.id()) {
}
- MyMoneyNotification(MyMoneyFile::notificationModeT mode, const MyMoneySecurity& security) :
- m_objType(MyMoneyFile::notifySecurity),
+ MyMoneyNotification(File::Mode mode, const MyMoneySecurity& security) :
+ m_objType(File::Object::Security),
m_notificationMode(mode),
m_id(security.id()) {
}
- MyMoneyNotification(MyMoneyFile::notificationModeT mode, const onlineJob& job) :
- m_objType(MyMoneyFile::notifyOnlineJob),
+ MyMoneyNotification(File::Mode mode, const onlineJob& job) :
+ m_objType(File::Object::OnlineJob),
m_notificationMode(mode),
m_id(job.id()) {
}
- MyMoneyFile::notificationObjectT objectType() const {
+ File::Object objectType() const {
return m_objType;
}
- MyMoneyFile::notificationModeT notificationMode() const {
+ File::Mode notificationMode() const {
return m_notificationMode;
}
const QString& id() const {
return m_id;
}
protected:
- MyMoneyNotification(MyMoneyFile::notificationObjectT obj,
- MyMoneyFile::notificationModeT mode,
+ MyMoneyNotification(File::Object obj,
+ File::Mode mode,
const QString& id) :
m_objType(obj),
m_notificationMode(mode),
m_id(id) {}
private:
- MyMoneyFile::notificationObjectT m_objType;
- MyMoneyFile::notificationModeT m_notificationMode;
+ 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<QString, bool>::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<MyMoneyAccount> accList;
file.accountList(accList);
QList<MyMoneyAccount>::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;
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<QString> 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<QString> m_valueChangedSet;
/**
* This member keeps the list of changes in the engine
* in historical order. The type can be 'added', 'modified'
* or removed.
*/
QList<MyMoneyNotification> 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) :
d(new Private)
{
attachStorage(storage);
}
void MyMoneyFile::attachStorage(IMyMoneyStorage* 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 */)
{
d->m_balanceCache.clear();
d->m_cache.clear();
d->m_priceCache.clear();
d->m_storage = 0;
}
IMyMoneyStorage* 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<MyMoneyNotification>::const_iterator it = d->m_changeSet.constBegin();
while (it != d->m_changeSet.constEnd()) {
- if ((*it).notificationMode() == notifyRemove) {
+ 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 MyMoneyFile::notifyTransaction:
+ 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() == notifyAdd) {
+ 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 MyMoneyAccount& 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 MyMoneyAccount& 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(notifyAdd, 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(notifyModify, institution);
+ 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;
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) {
// the following line will throw an exception if the
// account does not exist
MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
if (acc.id().isEmpty())
throw MYMONEYEXCEPTION("Cannot store split with no account assigned");
if (isStandardAccount((*it_s).accountId()))
throw MYMONEYEXCEPTION("Cannot store split referencing standard account");
if (acc.isLoan() && ((*it_s).action() == MyMoneySplit::ActionTransfer))
loanAccountAffected = true;
}
// change transfer splits between asset/liability and loan accounts
// into amortization splits
if (loanAccountAffected) {
QList<MyMoneySplit> list = transaction.splits();
for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) {
if ((*it_s).action() == MyMoneySplit::ActionTransfer) {
MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
if (acc.isAssetLiability()) {
MyMoneySplit s = (*it_s);
s.setAction(MyMoneySplit::ActionAmortization);
tCopy.modifySplit(s);
}
}
}
}
// 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
for (it_s = tr.splits().constBegin(); it_s != tr.splits().constEnd(); ++it_s) {
d->addCacheNotification((*it_s).accountId(), tr.postDate());
d->addCacheNotification((*it_s).payeeId());
//FIXME-ALEX Do I need to add d->addCacheNotification((*it_s).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
for (it_s = tCopy.splits().constBegin(); it_s != tCopy.splits().constEnd(); ++it_s) {
d->addCacheNotification((*it_s).accountId(), tCopy.postDate());
d->addCacheNotification((*it_s).payeeId());
//FIXME-ALEX Do I need to add d->addCacheNotification((*it_s).tagList()); ??
}
- d->m_changeSet += MyMoneyNotification(notifyModify, transaction);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Modify, transaction);
}
void MyMoneyFile::modifyAccount(const MyMoneyAccount& _account)
{
d->checkTransaction(Q_FUNC_INFO);
MyMoneyAccount account(_account);
MyMoneyAccount acc = MyMoneyFile::account(account.id());
// check that for standard accounts only specific parameters are changed
if (isStandardAccount(account.id())) {
// make sure to use the stuff we found on file
account = acc;
// and only use the changes that are allowed
account.setName(_account.name());
account.setCurrencyId(_account.currencyId());
// now check that it is the same
if (!(account == _account))
throw 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(notifyModify, account);
+ 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() == MyMoneyAccount::Income && parent.accountType() == MyMoneyAccount::Expense)
- || (acc.accountType() == MyMoneyAccount::Expense && parent.accountType() == MyMoneyAccount::Income)) {
+ || (acc.accountType() == Account::Income && parent.accountType() == Account::Expense)
+ || (acc.accountType() == Account::Expense && parent.accountType() == Account::Income)) {
- if (acc.isInvest() && parent.accountType() != MyMoneyAccount::Investment)
+ if (acc.isInvest() && parent.accountType() != Account::Investment)
throw MYMONEYEXCEPTION("Unable to reparent Stock to non-investment account");
- if (parent.accountType() == MyMoneyAccount::Investment && !acc.isInvest())
+ if (parent.accountType() == Account::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(notifyModify, curParent);
- d->m_changeSet += MyMoneyNotification(notifyModify, parent);
- d->m_changeSet += MyMoneyNotification(notifyModify, acc);
+ 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");
}
const MyMoneyInstitution& MyMoneyFile::institution(const QString& id) const
{
return d->m_cache.institution(id);
}
const MyMoneyAccount& MyMoneyFile::account(const QString& id) const
{
return d->m_cache.account(id);
}
const MyMoneyAccount& MyMoneyFile::subAccountByName(const MyMoneyAccount& acc, const QString& name) const
{
static MyMoneyAccount nullAccount;
QList<QString>::const_iterator it_a;
for (it_a = acc.accountList().constBegin(); it_a != acc.accountList().constEnd(); ++it_a) {
const MyMoneyAccount& sacc = account(*it_a);
if (sacc.name() == name)
return sacc;
}
return nullAccount;
}
const MyMoneyAccount& MyMoneyFile::accountByName(const QString& name) const
{
return d->m_cache.accountByName(name);
}
void MyMoneyFile::removeTransaction(const MyMoneyTransaction& transaction)
{
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());
QList<MyMoneySplit>::ConstIterator it_s;
// scan the splits again to update notification list
for (it_s = tr.splits().constBegin(); it_s != tr.splits().constEnd(); ++it_s) {
MyMoneyAccount acc = account((*it_s).accountId());
if (acc.isClosed())
throw MYMONEYEXCEPTION(i18n("Cannot remove transaction that references a closed account."));
d->addCacheNotification((*it_s).accountId(), tr.postDate());
d->addCacheNotification((*it_s).payeeId());
//FIXME-ALEX Do I need to add d->addCacheNotification((*it_s).tagList()); ??
}
d->m_storage->removeTransaction(transaction);
// remove a possible notification of that same object from the changeSet
QList<MyMoneyNotification>::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(notifyRemove, transaction);
+ 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);
MyMoneyAccount acc = account(id);
d->m_storage->setAccountName(id, name);
d->addCacheNotification(id);
- d->m_changeSet += MyMoneyNotification(notifyModify, acc);
+ 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 MyMoneyAccount& acc = MyMoneyFile::account(id);
- d->m_changeSet += MyMoneyNotification(notifyModify, acc);
+ 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(notifyModify, 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(notifyModify, parent);
- d->m_changeSet += MyMoneyNotification(notifyRemove, acc);
+ 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
for (QStringList::ConstIterator it = account_list.constBegin(); it != account_list.constEnd(); ++it) {
MyMoneyAccount a = d->m_storage->account(*it);
//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(*it);
}
// 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
for (QStringList::ConstIterator it = account_list.constBegin(); it != account_list.constEnd(); ++it) {
if (transactionCount(*it) != 0)
return false; // the current account has a transaction assigned
if (!hasOnlyUnusedAccounts(account(*it).accountList(), level + 1))
return false; // some sub-account has a transaction assigned
}
return true; // all subaccounts unused
}
void MyMoneyFile::removeInstitution(const MyMoneyInstitution& institution)
{
d->checkTransaction(Q_FUNC_INFO);
// clear all changed objects from cache
MyMoneyNotifier notifier(d);
QList<QString>::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) {
MyMoneyAccount acc = account(*it_a);
acc.setInstitutionId(QString());
modifyAccount(acc);
- d->m_changeSet += MyMoneyNotification(notifyModify, acc);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Modify, acc);
}
blockSignals(blocked);
d->m_storage->removeInstitution(institution);
- d->m_changeSet += MyMoneyNotification(notifyRemove, 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::AccountSeperator)) != -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() == MyMoneyAccount::Loan
- || newAccount.accountType() == MyMoneyAccount::AssetLoan)
+ if ((newAccount.accountType() == Account::Loan
+ || newAccount.accountType() == Account::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() == MyMoneyAccount::Loan)
+ if (acc.accountType() == Account::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() == MyMoneyAccount::Investment
+ } else if (newAccount.accountType() == Account::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() == MyMoneyAccount::UnknownAccountType)
+ if (account.accountType() == Account::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
MyMoneyAccount acc = MyMoneyFile::account(parent.id());
#if 0
// TODO: remove the following code as we now can have multiple accounts
// with the same name even in the same hierarchy position of the account tree
//
// check if the selected name is currently not among the child accounts
// if we find one, then return it as the new account
QStringList::const_iterator it_a;
for (it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) {
MyMoneyAccount a = MyMoneyFile::account(*it_a);
if (account.name() == a.name()) {
account = a;
return;
}
}
#endif
// FIXME: make sure, that the parent has the same type
// I left it out here because I don't know, if there is
// a tight coupling between e.g. checking accounts and the
// class asset. It certainly does not make sense to create an
// expense account under an income account. Maybe it does, I don't know.
// We enforce, that a stock account can never be a parent and
// that the parent for a stock account must be an investment. Also,
// an investment cannot have another investment account as it's parent
if (parent.isInvest())
throw MYMONEYEXCEPTION("Stock account cannot be parent account");
- if (account.isInvest() && parent.accountType() != MyMoneyAccount::Investment)
+ if (account.isInvest() && parent.accountType() != Account::Investment)
throw MYMONEYEXCEPTION("Stock account must have investment account as parent ");
- if (!account.isInvest() && parent.accountType() == MyMoneyAccount::Investment)
+ if (!account.isInvest() && parent.accountType() == Account::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(notifyAdd, account);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Add, account);
d->m_storage->addAccount(parent, account);
- d->m_changeSet += MyMoneyNotification(notifyModify, parent);
+ 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(notifyModify, 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<MyMoneyTransaction> transactions = transactionList(filter);
QList<MyMoneyTransaction>::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;
}
const 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;
}
}
const MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) const
{
return openingBalanceAccount_internal(security);
}
const MyMoneyAccount MyMoneyFile::openingBalanceAccount_internal(const MyMoneySecurity& security) const
{
if (!security.isCurrency())
throw MYMONEYEXCEPTION("Opening balance for non currencies not supported");
MyMoneyAccount acc;
QList<MyMoneyAccount> accounts;
QList<MyMoneyAccount>::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;
}
const MyMoneyAccount MyMoneyFile::createOpeningBalanceAccount(const MyMoneySecurity& security)
{
d->checkTransaction(Q_FUNC_INFO);
MyMoneyAccount acc;
QList<MyMoneyAccount> accounts;
QList<MyMoneyAccount>::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(MyMoneyAccount::Equity);
+ acc.setAccountType(Account::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
bool loanAccountAffected = false;
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) {
// the following line will throw an exception if the
// account does not exist or is one of the standard accounts
MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
if (acc.id().isEmpty())
throw MYMONEYEXCEPTION("Cannot add split with no account assigned");
if (acc.isLoan())
loanAccountAffected = true;
if (isStandardAccount((*it_s).accountId()))
throw MYMONEYEXCEPTION("Cannot add split referencing standard account");
}
// change transfer splits between asset/liability and loan accounts
// into amortization splits
if (loanAccountAffected) {
QList<MyMoneySplit> list = transaction.splits();
for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) {
if ((*it_s).action() == MyMoneySplit::ActionTransfer) {
MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
if (acc.isAssetLiability()) {
MyMoneySplit s = (*it_s);
s.setAction(MyMoneySplit::ActionAmortization);
transaction.modifySplit(s);
}
}
}
}
// check that we have a commodity
if (transaction.commodity().isEmpty()) {
transaction.setCommodity(baseCurrency().id());
}
// 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
for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) {
d->addCacheNotification((*it_s).accountId(), transaction.postDate());
d->addCacheNotification((*it_s).payeeId());
//FIXME-ALEX Do I need to add d->addCacheNotification((*it_s).tagList()); ??
}
- d->m_changeSet += MyMoneyNotification(notifyAdd, transaction);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Add, transaction);
}
const MyMoneyTransaction MyMoneyFile::transaction(const QString& id) const
{
d->checkStorage();
return d->m_storage->transaction(id);
}
const 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(notifyAdd, payee);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Add, payee);
}
const MyMoneyPayee& MyMoneyFile::payee(const QString& id) const
{
return d->m_cache.payee(id);
}
const 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(notifyModify, 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(notifyRemove, payee);
+ 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(notifyAdd, tag);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Add, tag);
}
const MyMoneyTag& MyMoneyFile::tag(const QString& id) const
{
return d->m_cache.tag(id);
}
const 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(notifyModify, 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(notifyRemove, tag);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Remove, tag);
}
void MyMoneyFile::accountList(QList<MyMoneyAccount>& list, const QStringList& idlist, const bool recursive) const
{
if (idlist.isEmpty()) {
d->m_cache.account(list);
#if 0
// TODO: I have no idea what this was good for, but it caused the networth report
// to show double the numbers so I commented it out (ipwizard, 2008-05-24)
if (d->m_storage && (list.isEmpty() || list.size() != d->m_storage->accountCount())) {
d->m_storage->accountList(list);
d->m_cache.preloadAccount(list);
}
#endif
QList<MyMoneyAccount>::Iterator it;
for (it = list.begin(); it != list.end();) {
if (isStandardAccount((*it).id())) {
it = list.erase(it);
} else {
++it;
}
}
} else {
QList<MyMoneyAccount>::ConstIterator it;
QList<MyMoneyAccount> 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<MyMoneyInstitution>& list) const
{
d->m_cache.institution(list);
}
const QList<MyMoneyInstitution> MyMoneyFile::institutionList() const
{
QList<MyMoneyInstitution> list;
institutionList(list);
return list;
}
// general get functions
const 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());
}
}
const MyMoneyAccount& MyMoneyFile::liability() const
{
d->checkStorage();
return d->m_cache.account(STD_ACC_LIABILITY);
}
const MyMoneyAccount& MyMoneyFile::asset() const
{
d->checkStorage();
return d->m_cache.account(STD_ACC_ASSET);
}
const MyMoneyAccount& MyMoneyFile::expense() const
{
d->checkStorage();
return d->m_cache.account(STD_ACC_EXPENSE);
}
const MyMoneyAccount& MyMoneyFile::income() const
{
d->checkStorage();
return d->m_cache.account(STD_ACC_INCOME);
}
const MyMoneyAccount& MyMoneyFile::equity() const
{
d->checkStorage();
return d->m_cache.account(STD_ACC_EQUITY);
}
unsigned int MyMoneyFile::transactionCount(const QString& account) const
{
d->checkStorage();
return d->m_storage->transactionCount(account);
}
+unsigned int MyMoneyFile::transactionCount() const
+{
+ return transactionCount(QString());
+}
const QMap<QString, unsigned long> MyMoneyFile::transactionCountMap() const
{
d->checkStorage();
return d->m_storage->transactionCountMap();
}
unsigned int MyMoneyFile::institutionCount() const
{
d->checkStorage();
return d->m_storage->institutionCount();
}
const 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;
}
+const MyMoneyMoney MyMoneyFile::balance(const QString& id) const
+{
+ return balance(id, QDate());
+}
const MyMoneyMoney MyMoneyFile::clearedBalance(const QString &id, const QDate& date) const
{
MyMoneyMoney cleared;
QList<MyMoneyTransaction> list;
cleared = balance(id, date);
MyMoneyAccount account = this->account(id);
MyMoneyMoney factor(1, 1);
- if (account.accountGroup() == MyMoneyAccount::Liability || account.accountGroup() == MyMoneyAccount::Equity)
+ if (account.accountGroup() == Account::Liability || account.accountGroup() == Account::Equity)
factor = -factor;
MyMoneyTransactionFilter filter;
filter.addAccount(id);
filter.setDateFilter(QDate(), date);
filter.setReportAllSplits(false);
- filter.addState(MyMoneyTransactionFilter::notReconciled);
+ filter.addState((int)TransactionFilter::State::NotReconciled);
transactionList(list, filter);
for (QList<MyMoneyTransaction>::const_iterator it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) {
const QList<MyMoneySplit>& splits = (*it_t).splits();
for (QList<MyMoneySplit>::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;
}
const MyMoneyMoney MyMoneyFile::totalBalance(const QString& id, const QDate& date) const
{
d->checkStorage();
return d->m_storage->totalBalance(id, date);
}
+const 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<QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const
{
d->checkStorage();
d->m_storage->transactionList(list, filter);
}
void MyMoneyFile::transactionList(QList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const
{
d->checkStorage();
d->m_storage->transactionList(list, filter);
}
const QList<MyMoneyTransaction> MyMoneyFile::transactionList(MyMoneyTransactionFilter& filter) const
{
QList<MyMoneyTransaction> list;
transactionList(list, filter);
return list;
}
const QList<MyMoneyPayee> MyMoneyFile::payeeList() const
{
QList<MyMoneyPayee> list;
d->m_cache.payee(list);
return list;
}
const QList<MyMoneyTag> MyMoneyFile::tagList() const
{
QList<MyMoneyTag> 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 = AccountSeperator + rc;
rc = acc.name() + rc;
acc = account(acc.parentAccountId());
} while (!acc.id().isEmpty() && (includeStandardAccounts || !isStandardAccount(acc.id())));
}
return rc;
}
-QString MyMoneyFile::categoryToAccount(const QString& category, MyMoneyAccount::accountTypeE type) const
+QString MyMoneyFile::categoryToAccount(const QString& category, Account type) const
{
QString id;
// search the category in the expense accounts and if it is not found, try
// to locate it in the income accounts
- if (type == MyMoneyAccount::UnknownAccountType
- || type == MyMoneyAccount::Expense) {
+ if (type == Account::Unknown
+ || type == Account::Expense) {
id = locateSubAccount(MyMoneyFile::instance()->expense(), category);
}
- if ((id.isEmpty() && type == MyMoneyAccount::UnknownAccountType)
- || type == MyMoneyAccount::Income) {
+ if ((id.isEmpty() && type == Account::Unknown)
+ || type == Account::Income) {
id = locateSubAccount(MyMoneyFile::instance()->income(), category);
}
return id;
}
QString MyMoneyFile::nameToAccount(const QString& name) const
{
QString id;
// search the category in the asset accounts and if it is not found, try
// to locate it in the liability accounts
id = locateSubAccount(MyMoneyFile::instance()->asset(), name);
if (id.isEmpty())
id = locateSubAccount(MyMoneyFile::instance()->liability(), name);
return id;
}
QString MyMoneyFile::parentName(const QString& name) const
{
return name.section(AccountSeperator, 0, -2);
}
QString MyMoneyFile::locateSubAccount(const MyMoneyAccount& base, const QString& category) const
{
MyMoneyAccount nextBase;
QString level, remainder;
level = category.section(AccountSeperator, 0, 0);
remainder = category.section(AccountSeperator, 1);
QStringList list = base.accountList();
QStringList::ConstIterator it_a;
for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
nextBase = account(*it_a);
if (nextBase.name() == level) {
if (remainder.isEmpty()) {
return nextBase.id();
}
return locateSubAccount(nextBase, remainder);
}
}
return QString();
}
QString MyMoneyFile::value(const QString& key) const
{
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);
MyMoneyTransaction transaction = sched.transaction();
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) {
// the following line will throw an exception if the
// account does not exist or is one of the standard accounts
MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
if (acc.id().isEmpty())
throw MYMONEYEXCEPTION("Cannot add split with no account assigned");
if (isStandardAccount((*it_s).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(notifyAdd, sched);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Add, sched);
}
void MyMoneyFile::modifySchedule(const MyMoneySchedule& sched)
{
d->checkTransaction(Q_FUNC_INFO);
MyMoneyTransaction transaction = sched.transaction();
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) {
// the following line will throw an exception if the
// account does not exist or is one of the standard accounts
MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
if (acc.id().isEmpty())
throw MYMONEYEXCEPTION("Cannot store split with no account assigned");
if (isStandardAccount((*it_s).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(notifyModify, sched);
+ 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(notifyRemove, sched);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Remove, sched);
}
const MyMoneySchedule MyMoneyFile::schedule(const QString& id) const
{
return d->m_cache.schedule(id);
}
const QList<MyMoneySchedule> MyMoneyFile::scheduleList(
const QString& accountId,
- const MyMoneySchedule::typeE type,
- const MyMoneySchedule::occurrenceE occurrence,
- const MyMoneySchedule::paymentTypeE paymentType,
+ 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);
}
+const QList<MyMoneySchedule> MyMoneyFile::scheduleList(
+ const QString& accountId) const
+{
+ return scheduleList(accountId, Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any,
+ QDate(), QDate(), false);
+}
+
+const QList<MyMoneySchedule> MyMoneyFile::scheduleList() const
+{
+ return scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any,
+ QDate(), QDate(), false);
+}
const QStringList MyMoneyFile::consistencyCheck()
{
QList<MyMoneyAccount> list;
QList<MyMoneyAccount>::Iterator it_a;
QList<MyMoneySchedule>::Iterator it_sch;
QList<MyMoneyPayee>::Iterator it_p;
QList<MyMoneyTransaction>::Iterator it_t;
QList<MyMoneyReport>::Iterator it_r;
QStringList accountRebuild;
QStringList::ConstIterator it_c;
QMap<QString, bool> interestAccounts;
MyMoneyAccount parent;
MyMoneyAccount child;
MyMoneyAccount toplevel;
QString parentId;
QStringList rc;
int problemCount = 0;
int unfixedCount = 0;
QString problemAccount;
// check that we have a storage object
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 MyMoneyAccount::Asset:
+ case Account::Asset:
toplevel = asset();
break;
- case MyMoneyAccount::Liability:
+ case Account::Liability:
toplevel = liability();
break;
- case MyMoneyAccount::Expense:
+ case Account::Expense:
toplevel = expense();
break;
- case MyMoneyAccount::Income:
+ case Account::Income:
toplevel = income();
break;
- case MyMoneyAccount::Equity:
+ case Account::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<MyMoneyAccount>::Iterator it_b;
for (it_b = list.begin(); it_b != list.end(); ++it_b) {
if ((*it_b).id() == parent.id()) {
if (problemAccount != (*it_a).name()) {
problemAccount = (*it_a).name();
rc << i18n("* Problem with account '%1'", 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
for (it_c = (*it_a).accountList().begin(); it_c != (*it_a).accountList().end(); ++it_c) {
// check that the child exists
try {
child = account(*it_c);
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.", *it_c);
rc << i18n(" The child account list will be reconstructed.");
if (accountRebuild.contains((*it_a).id()) == 0)
accountRebuild << (*it_a).id();
}
}
// see if it is a loan account. if so, remember the assigned interest account
if ((*it_a).isLoan()) {
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
for (it_c = (*it_a).accountList().begin(); it_c != (*it_a).accountList().end();) {
(*it_a).removeAccountId(*it_c);
it_c = (*it_a).accountList().begin();
}
}
}
// reconstruct the lists
for (it_a = list.begin(); it_a != list.end(); ++it_a) {
QList<MyMoneyAccount>::Iterator it;
parentId = (*it_a).parentAccountId();
if (accountRebuild.contains(parentId)) {
for (it = list.begin(); it != list.end(); ++it) {
if ((*it).id() == parentId) {
(*it).addAccountId((*it_a).id());
break;
}
}
}
}
// update the engine objects
for (it_a = list.begin(); it_a != list.end(); ++it_a) {
if (accountRebuild.contains((*it_a).id())) {
try {
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<MyMoneyPayee> pList = payeeList();
QMap<QString, QString>payeeConversionMap;
for (it_p = pList.begin(); it_p != pList.end(); ++it_p) {
if ((*it_p).id().length() > 7) {
// found one of those with an invalid ids
// create a new one and store it in the map.
MyMoneyPayee payee = (*it_p);
payee.clearId();
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<MyMoneyTransaction> tList;
MyMoneyTransactionFilter filter;
filter.setReportAllSplits(false);
d->m_storage->transactionList(tList, filter);
// Generate the list of interest accounts
for (it_t = tList.begin(); it_t != tList.end(); ++it_t) {
const MyMoneyTransaction& t = (*it_t);
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
if ((*it_s).action() == MyMoneySplit::ActionInterest)
interestAccounts[(*it_s).accountId()] = true;
}
}
- QSet<MyMoneyAccount::accountTypeE> supportedAccountTypes;
- supportedAccountTypes << MyMoneyAccount::Checkings
- << MyMoneyAccount::Savings
- << MyMoneyAccount::Cash
- << MyMoneyAccount::CreditCard
- << MyMoneyAccount::Asset
- << MyMoneyAccount::Liability;
+ QSet<Account> supportedAccountTypes;
+ supportedAccountTypes << Account::Checkings
+ << Account::Savings
+ << Account::Cash
+ << Account::CreditCard
+ << Account::Asset
+ << Account::Liability;
QSet<QString> reportedUnsupportedAccounts;
for (it_t = tList.begin(); it_t != tList.end(); ++it_t) {
MyMoneyTransaction t = (*it_t);
QList<MyMoneySplit> splits = t.splits();
QList<MyMoneySplit>::const_iterator it_s;
bool tChanged = false;
QDate accountOpeningDate;
QStringList accountList;
for (it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) {
bool sChanged = false;
MyMoneySplit s = (*it_s);
if (payeeConversionMap.find((*it_s).payeeId()) != payeeConversionMap.end()) {
s.setPayeeId(payeeConversionMap[s.payeeId()]);
sChanged = true;
rc << i18n(" * Payee id updated in split of transaction '%1'.", t.id());
++problemCount;
}
try {
const MyMoneyAccount& 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(), (*it_s).id(), (*it_s).accountId());
++unfixedCount;
}
// make sure the interest splits are marked correct as such
if (interestAccounts.find(s.accountId()) != interestAccounts.end()
&& s.action() != MyMoneySplit::ActionInterest) {
s.setAction(MyMoneySplit::ActionInterest);
sChanged = true;
rc << i18n(" * action marked as interest in split of transaction '%1'.", 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<MyMoneySplit>::const_iterator it_t;
for (it_t = t.splits().constBegin(); it_t != t.splits().constEnd(); ++it_t) {
if (((*it_t).action() != "Buy") &&
((*it_t).action() != "Reinvest")) {
continue;
}
QString id = (*it_t).accountId();
MyMoneyAccount acc = this->account(id);
MyMoneySecurity sec = this->security(acc.currencyId());
MyMoneyPrice price(acc.currencyId(),
sec.tradingCurrency(),
t.postDate(),
(*it_t).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<MyMoneySchedule> schList = scheduleList();
for (it_sch = schList.begin(); it_sch != schList.end(); ++it_sch) {
MyMoneySchedule sch = (*it_sch);
MyMoneyTransaction t = sch.transaction();
QList<MyMoneySplit> splits = t.splits();
bool tChanged = false;
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) {
MyMoneySplit s = (*it_s);
bool sChanged = false;
if (payeeConversionMap.find((*it_s).payeeId()) != payeeConversionMap.end()) {
s.setPayeeId(payeeConversionMap[s.payeeId()]);
sChanged = true;
rc << i18n(" * Payee id updated in split of schedule '%1'.", (*it_sch).name());
++problemCount;
}
if (!(*it_s).value().isZero() && (*it_s).shares().isZero()) {
s.setShares(s.value());
sChanged = true;
rc << i18n(" * Split in scheduled transaction '%1' contained value != 0 and shares == 0.", (*it_sch).name());
rc << i18n(" Shares set to value.");
++problemCount;
}
// make sure, we don't have a bankid stored with a split in a schedule
if (!(*it_s).bankID().isEmpty()) {
s.setBankID(QString());
sChanged = true;
rc << i18n(" * Removed bankid from split in scheduled transaction '%1'.", (*it_sch).name());
++problemCount;
}
// make sure, that shares and value have the same number if they
// represent the same currency.
try {
const MyMoneyAccount& acc = this->account(s.accountId());
if (t.commodity() == acc.currencyId()
&& s.shares().reduce() != s.value().reduce()) {
// use the value as master if the transaction is balanced
if (t.splitSum().isZero()) {
s.setShares(s.value());
rc << i18n(" * shares set to value in split in schedule '%1'.", (*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(), (*it_s).id(), (*it_s).accountId());
++unfixedCount;
}
if (sChanged) {
t.modifySplit(s);
tChanged = true;
}
}
if (tChanged) {
sch.setTransaction(t);
d->m_storage->modifySchedule(sch);
}
}
// Fix the reports
QList<MyMoneyReport> rList = reportList();
for (it_r = rList.begin(); it_r != rList.end(); ++it_r) {
MyMoneyReport r = *it_r;
QStringList pList;
QStringList::Iterator it_p;
(*it_r).payees(pList);
bool rChanged = false;
for (it_p = pList.begin(); it_p != pList.end(); ++it_p) {
if (payeeConversionMap.find(*it_p) != payeeConversionMap.end()) {
rc << i18n(" * Payee id updated in report '%1'.", (*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<QString, QString>::Iterator it_m;
for (it_m = payeeConversionMap.begin(); it_m != payeeConversionMap.end(); ++it_m) {
MyMoneyPayee payee = this->payee(it_m.key());
removePayee(payee);
rc << i18n(" * Payee '%1' removed.", 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<MyMoneyAccount> accountForeignCurrency;
QList<MyMoneyAccount> accList;
accountList(accList);
QList<MyMoneyAccount>::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<MyMoneySecurityPair, QDate> 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<QString, QString> pricePair = qMakePair(firstPrice.from(), firstPrice.to());
securityPriceDate[pricePair] = firstPrice.date();
}
}
//compare the dates with the opening dates of the accounts using each currency
QList<MyMoneyAccount>::const_iterator accForeignList_it;
bool firstInvProblem = true;
for (accForeignList_it = accountForeignCurrency.constBegin(); accForeignList_it != accountForeignCurrency.constEnd(); ++accForeignList_it) {
//setup the price pair correctly
QPair<QString, QString> pricePair;
//setup the reverse, which can also be used for rate conversion
QPair<QString, QString> 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<MyMoneyBudget> bList = budgetList();
for (QList<MyMoneyBudget>::const_iterator it_b = bList.constBegin(); it_b != bList.constEnd(); ++it_b) {
MyMoneyBudget b = *it_b;
QList<MyMoneyBudget::AccountGroup> baccounts = b.getaccounts();
bool bChanged = false;
for (QList<MyMoneyBudget::AccountGroup>::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(AccountSeperator);
QStringList::Iterator it;
for (it = subAccounts.begin(); it != subAccounts.end(); ++it) {
MyMoneyAccount categoryAccount;
categoryAccount.setName(*it);
categoryAccount.setAccountType(base.accountType());
if (it == subAccounts.begin())
categoryText += *it;
else
categoryText += (AccountSeperator + *it);
// Only create the account if it doesn't exist
try {
QString categoryId = categoryToAccount(categoryText);
if (categoryId.isEmpty())
addAccount(categoryAccount, parent);
else {
categoryAccount = account(categoryId);
}
} catch (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()) ? MyMoneyAccount::Income : MyMoneyAccount::Expense);
+ newAccount.setAccountType((!value.isNegative() && value2.isNegative()) ? Account::Income : Account::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;
}
const QList<MyMoneySchedule> MyMoneyFile::scheduleListEx(int scheduleTypes,
int scheduleOcurrences,
int schedulePaymentTypes,
QDate startDate,
const QStringList& accounts) const
{
d->checkStorage();
return d->m_storage->scheduleListEx(scheduleTypes, scheduleOcurrences, schedulePaymentTypes, startDate, accounts);
}
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(notifyAdd, 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(notifyModify, security);
+ 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(notifyRemove, security);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Remove, security);
}
const MyMoneySecurity& MyMoneyFile::security(const QString& id) const
{
if (id.isEmpty())
return baseCurrency();
return d->m_cache.security(id);
}
const QList<MyMoneySecurity> 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(notifyAdd, 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(notifyModify, currency);
+ 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(notifyRemove, currency);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Remove, currency);
}
const MyMoneySecurity& MyMoneyFile::currency(const QString& id) const
{
if (id.isEmpty())
return baseCurrency();
const MyMoneySecurity& curr = d->m_cache.security(id);
if (curr.id().isEmpty()) {
QString msg;
msg = QString("Currency '%1' not found.").arg(id);
throw MYMONEYEXCEPTION(msg);
}
return curr;
}
const QMap<MyMoneySecurity, MyMoneyPrice> MyMoneyFile::ancientCurrencies() const
{
QMap<MyMoneySecurity, MyMoneyPrice> ancientCurrencies;
ancientCurrencies.insert(MyMoneySecurity("ATS", i18n("Austrian Schilling"), "Ö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;
}
const QList<MyMoneySecurity> MyMoneyFile::availableCurrencyList() const
{
QList<MyMoneySecurity> 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(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));
currencyList.append(ancientCurrencies().keys());
return currencyList;
}
const QList<MyMoneySecurity> MyMoneyFile::currencyList() const
{
d->checkStorage();
return d->m_storage->currencyList();
}
const QString& MyMoneyFile::foreignCurrency(const QString& first, const QString& second) const
{
if (baseCurrency().id() == second)
return first;
return second;
}
const MyMoneySecurity& MyMoneyFile::baseCurrency() 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);
+}
+
const MyMoneyPriceList MyMoneyFile::priceList() const
{
d->checkStorage();
return d->m_storage->priceList();
}
bool MyMoneyFile::hasAccount(const QString& id, const QString& name) const
{
MyMoneyAccount acc = d->m_cache.account(id);
QStringList list = acc.accountList();
QStringList::ConstIterator it;
bool rc = false;
for (it = list.constBegin(); rc == false && it != list.constEnd(); ++it) {
MyMoneyAccount a = d->m_cache.account(*it);
if (a.name() == name)
rc = true;
}
return rc;
}
const QList<MyMoneyReport> 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();
}
const 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);
}
const QList<MyMoneyBudget> 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);
}
const 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();
}
const 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(notifyAdd, 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(notifyModify, job);
+ d->m_changeSet += MyMoneyNotification(File::Mode::Modify, job);
d->addCacheNotification(job.id());
}
const onlineJob MyMoneyFile::getOnlineJob(const QString &jobId) const
{
d->checkStorage();
return d->m_storage->getOnlineJob(jobId);
}
const QList<onlineJob> 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(notifyRemove, job);
+ 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<MyMoneyTransaction> transactions = transactionList(filter);
QList<MyMoneyTransaction>::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<MyMoneyTransaction> transactions = transactionList(filter);
QList<MyMoneyTransaction>::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());
QList<MyMoneyTransaction> transactions = transactionList(filter);
return transactions.count() > 0;
}
void MyMoneyFile::clearCache()
{
d->checkStorage();
d->m_cache.clear();
d->m_balanceCache.clear();
}
void MyMoneyFile::preloadCache()
{
d->checkStorage();
d->m_cache.clear();
QList<MyMoneyAccount> 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());
d->m_cache.preloadOnlineJob(d->m_storage->onlineJobList());
}
bool MyMoneyFile::isTransfer(const MyMoneyTransaction& t) const
{
bool rc = false;
if (t.splitCount() == 2) {
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
MyMoneyAccount acc = account((*it_s).accountId());
if (acc.isIncomeExpense())
break;
}
if (it_s == t.splits().end())
rc = true;
}
return rc;
}
bool MyMoneyFile::referencesClosedAccount(const MyMoneyTransaction& t) const
{
QList<MyMoneySplit>::const_iterator it_s;
const QList<MyMoneySplit>& list = t.splits();
for (it_s = list.begin(); it_s != list.end(); ++it_s) {
if (referencesClosedAccount(*it_s))
break;
}
return it_s != list.end();
}
bool MyMoneyFile::referencesClosedAccount(const MyMoneySplit& s) const
{
if (s.accountId().isEmpty())
return false;
try {
return account(s.accountId()).isClosed();
} catch (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;
}
const QString MyMoneyFile::openingBalancesPrefix()
{
return i18n("Opening Balances");
}
bool MyMoneyFile::hasMatchingOnlineBalance(const MyMoneyAccount& _acc) const
{
// get current values
MyMoneyAccount 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, enum MyMoneyTransactionFilter::stateOptionE state) const
+int MyMoneyFile::countTransactionsWithSpecificReconciliationState(const QString& accId, TransactionFilter::State state) const
{
MyMoneyTransactionFilter filter;
filter.addAccount(accId);
- filter.addState(state);
+ 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
{
QList<MyMoneySplit>::iterator its;
MyMoneySecurity transactionSecurity = security(t.commodity());
int transactionFraction = transactionSecurity.smallestAccountFraction();
for(its = t.splits().begin(); its != t.splits().end(); ++its) {
MyMoneyAccount acc = account((*its).accountId());
int fraction = acc.fraction();
if(fraction == -1) {
MyMoneySecurity sec = security(acc.currencyId());
fraction = acc.fraction(sec);
}
(*its).setShares((*its).shares().convertDenominator(fraction).canonicalize());
(*its).setValue((*its).value().convertDenominator(transactionFraction).canonicalize());
}
}
MyMoneyFileTransaction::MyMoneyFileTransaction() :
m_isNested(MyMoneyFile::instance()->hasTransaction()),
m_needRollback(!m_isNested)
{
if (!m_isNested)
MyMoneyFile::instance()->startTransaction();
}
MyMoneyFileTransaction::~MyMoneyFileTransaction()
{
rollback();
}
void MyMoneyFileTransaction::restart()
{
rollback();
m_needRollback = !m_isNested;
if (!m_isNested)
MyMoneyFile::instance()->startTransaction();
}
void MyMoneyFileTransaction::commit()
{
if (!m_isNested)
MyMoneyFile::instance()->commitTransaction();
m_needRollback = false;
}
void MyMoneyFileTransaction::rollback()
{
if (m_needRollback)
MyMoneyFile::instance()->rollbackTransaction();
m_needRollback = false;
}
diff --git a/kmymoney/mymoney/mymoneyfile.h b/kmymoney/mymoney/mymoneyfile.h
index 9bad74d19..82520e0e9 100644
--- a/kmymoney/mymoney/mymoneyfile.h
+++ b/kmymoney/mymoney/mymoneyfile.h
@@ -1,1719 +1,1711 @@
/***************************************************************************
mymoneyfile.h
-------------------
copyright : (C) 2002, 2007 by Thomas Baumgart <ipwizard@users.sourceforge.net>
+ (C) 2017 by Łukasz Wojniłowicz <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 MYMONEYFILE_H
#define MYMONEYFILE_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QObject>
-#include <QString>
-#include <QMap>
-#include <QStringList>
// ----------------------------------------------------------------------------
// Project Includes
-
-#include "mymoneyinstitution.h"
-#include "mymoneyaccount.h"
-#include "mymoneytransaction.h"
-#include "mymoneypayee.h"
-#include "mymoneytag.h"
-#include "mymoneysecurity.h"
-#include "mymoneyprice.h"
-#include "mymoneyreport.h"
-#include <mymoneybudget.h>
-#include <onlinejob.h>
-#include "mymoneyschedule.h"
#include "kmm_mymoney_export.h"
#include "mymoneyunittestable.h"
+#include "mymoneyenums.h"
+
+using namespace eMyMoney;
/**
* @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
* 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 = ....
* 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 Key, class T> class QMap;
+class QString;
+class QStringList;
+class QBitArray;
class IMyMoneyStorage;
class MyMoneyCostCenter;
-class QBitArray;
+class MyMoneyAccount;
+class MyMoneyInstitution;
+class MyMoneySecurity;
+class MyMoneyPrice;
+typedef QPair<QString, QString> MyMoneySecurityPair;
+typedef QMap<QDate, MyMoneyPrice> MyMoneyPriceEntries;
+typedef QMap<MyMoneySecurityPair, MyMoneyPriceEntries> MyMoneyPriceList;
+class MyMoneySchedule;
+class MyMoneyTag;
+class MyMoneyPayee;
+class MyMoneyBudget;
+class MyMoneyReport;
+class MyMoneyMoney;
+class MyMoneySplit;
+class MyMoneyObject;
+class MyMoneyTransaction;
+class MyMoneyTransactionFilter;
+class onlineJob;
class KMM_MYMONEY_EXPORT MyMoneyFile : public QObject
{
Q_OBJECT
KMM_MYMONEY_UNIT_TESTABLE
public:
- /**
- * notificationObject identifies the type of the object
- * for which this notification is stored
- */
- typedef enum {
- notifyAccount = 1,
- notifyInstitution,
- notifyPayee,
- notifyTransaction,
- notifyTag,
- notifySchedule,
- notifySecurity,
- notifyOnlineJob
- } notificationObjectT;
-
- /**
- * notificationMode identifies the type of notifiation
- * (add, modify, remove)
- */
- typedef enum {
- notifyAdd = 1,
- notifyModify,
- notifyRemove
- } notificationModeT;
-
friend class MyMoneyNotifier;
/**
* This is the function to access the MyMoneyFile object.
* It returns a pointer to the single instance of the object.
*/
static inline MyMoneyFile* instance() {
return &file;
}
/**
* 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
* interface.
*/
MyMoneyFile(IMyMoneyStorage *storage);
// general get functions
const 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
* interface.
*
* @sa detachStorage()
*/
void attachStorage(IMyMoneyStorage* 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
* interface.
*
* @sa attachStorage()
*/
void detachStorage(IMyMoneyStorage* 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;
/**
* 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)
*/
const MyMoneyAccount& liability() const;
/**
* This method is used to return the standard asset account
* @return MyMoneyAccount asset account(group)
*/
const MyMoneyAccount& asset() const;
/**
* This method is used to return the standard expense account
* @return MyMoneyAccount expense account(group)
*/
const MyMoneyAccount& expense() const;
/**
* This method is used to return the standard income account
* @return MyMoneyAccount income account(group)
*/
const MyMoneyAccount& income() const;
/**
* This method is used to return the standard equity account
* @return MyMoneyAccount equity account(group)
*/
const 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!
*/
const 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!
*/
const 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
*/
const 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
*/
const 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<MyMoneyTransaction>
*/
const QList<MyMoneyTransaction> transactionList(MyMoneyTransactionFilter& filter) const;
void transactionList(QList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const;
void transactionList(QList<QPair<MyMoneyTransaction, MyMoneySplit> >& 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
*/
- const MyMoneyMoney balance(const QString& id, const QDate& date = QDate()) const;
+ const MyMoneyMoney balance(const QString& id, const QDate& date) const;
+ 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
*/
const 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
*/
- const MyMoneyMoney totalBalance(const QString& id, const QDate& date = QDate()) const;
+ const MyMoneyMoney totalBalance(const QString& id, const QDate& date) const;
+ 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 = QString()) const;
+ 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
*/
const QMap<QString, unsigned long> 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
*/
const 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<MyMoneyInstitution>& 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
*/
const QList<MyMoneyInstitution> 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
*/
const 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.
*/
const 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.
*/
const 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<MyMoneyAccount>& 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 MyMoneyAccount::AccountSeperator.
*
* @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
* MyMoneyAccount::AccountSeperator.
*
* @param category const reference to QString containing the category
- * @param type account type if a specific type is required (defaults to UnknownAccountType)
+ * @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, MyMoneyAccount::accountTypeE type = MyMoneyAccount::UnknownAccountType) const;
+ QString categoryToAccount(const QString& category, eMyMoney::Account type = eMyMoney::Account::Unknown) 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 MyMoneyAccount::AccountSeperator.
*
* @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 MyMoneyAccount::AccountSeperator.
*
* @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
*/
const 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
*/
const 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<MyMoneyPayee> containing the payee information
*/
const QList<MyMoneyPayee> 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
*/
const 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
*/
const 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<MyMoneyTag> containing the tag information
*/
const QList<MyMoneyTag> tagList() const;
/**
* This method returns a list of the cost centers
* inside a MyMoneyStorage object
*
* @return QList<MyMoneyCostCenter> 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
*/
const 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 MyMoneySchedule::typeE for details.
- * Default is MyMoneySchedule::TYPE_ANY
+ * 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 MyMoneySchedule::occurrenceE for details.
- * Default is MyMoneySchedule::OCCUR_ANY
+ * See eMyMoney::Schedule::Occurence for details.
+ * Default is eMyMoney::Schedule::Occurrence::Any
* @param paymentType only schedules of payment method @p paymentType
* are searched for.
- * See MyMoneySchedule::paymentTypeE for details.
- * Default is MyMoneySchedule::STYPE_ANY
+ * 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<MyMoneySchedule> list of schedule objects.
*/
- const QList<MyMoneySchedule> scheduleList(const QString& accountId = QString(),
- const MyMoneySchedule::typeE type = MyMoneySchedule::TYPE_ANY,
- const MyMoneySchedule::occurrenceE occurrence = MyMoneySchedule::OCCUR_ANY,
- const MyMoneySchedule::paymentTypeE paymentType = MyMoneySchedule::STYPE_ANY,
- const QDate& startDate = QDate(),
- const QDate& endDate = QDate(),
- const bool overdue = false) const;
+ const QList<MyMoneySchedule> 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;
+ const QList<MyMoneySchedule> scheduleList(const QString& accountId) const;
+ const QList<MyMoneySchedule> scheduleList() const;
const QStringList consistencyCheck();
/**
* MyMoneyFile::openingBalancesPrefix() is a special string used
* to generate the name for opening balances accounts. See openingBalanceAccount()
* for details.
*/
static const QString openingBalancesPrefix();
/**
* MyMoneyFile::AccountSeperator is used as the separator
* between account names to form a hierarchy.
*/
static const QString AccountSeperator;
/**
* 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);
const QList<MyMoneySchedule> scheduleListEx(int scheduleTypes,
int scheduleOcurrences,
int schedulePaymentTypes,
QDate startDate,
const QStringList& accounts = QStringList()) const;
/**
* 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
*/
const MyMoneySecurity& security(const QString& id) const;
/**
* This method is used to retrieve a list of all MyMoneySecurity objects.
*/
const QList<MyMoneySecurity> 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
*/
const 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.
*/
const QMap<MyMoneySecurity, MyMoneyPrice> 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.
*/
const QList<MyMoneySecurity> 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.
*/
const QList<MyMoneySecurity> 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
*/
const 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.
*/
const 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 = QString(), const QDate& date = QDate::currentDate(), const bool exactDate = false) const;
+ 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.
*/
const 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.
*/
const QList<MyMoneyReport> 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
*/
const 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.
*/
const QList<MyMoneyBudget> 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
*/
const 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
*/
const 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() {
emit dataChanged();
}
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, enum MyMoneyTransactionFilter::stateOptionE state) const;
+ int countTransactionsWithSpecificReconciliationState(const QString& accId, 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
*/
const onlineJob getOnlineJob(const QString &jobId) const;
/**
* @brief Returns all onlineJobs
* @return all online jobs, caller gains ownership
*/
const QList<onlineJob> 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(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const 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(MyMoneyFile::notificationObjectT objType, const QString& id);
+ 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(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const 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
*/
const MyMoneyAccount createOpeningBalanceAccount(const MyMoneySecurity& security);
const 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 KMM_MYMONEY_EXPORT 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:
bool m_isNested;
bool m_needRollback;
};
#endif
diff --git a/kmymoney/mymoney/mymoneyforecast.cpp b/kmymoney/mymoney/mymoneyforecast.cpp
index 8f79860f1..c8aba3e02 100644
--- a/kmymoney/mymoney/mymoneyforecast.cpp
+++ b/kmymoney/mymoney/mymoneyforecast.cpp
@@ -1,1269 +1,1274 @@
/***************************************************************************
mymoneyforecast.cpp
-------------------
begin : Wed May 30 2007
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.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QString>
#include <QList>
#include <QDebug>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
+#include "mymoneybudget.h"
+#include "mymoneyschedule.h"
+#include "mymoneyprice.h"
+#include "mymoneytransaction.h"
#include "mymoneytransactionfilter.h"
#include "mymoneyfinancialcalculator.h"
MyMoneyForecast::MyMoneyForecast() :
m_accountsCycle(30),
m_forecastCycles(3),
m_forecastDays(90),
m_beginForecastDay(0),
m_forecastMethod(eScheduled),
m_historyMethod(1),
m_skipOpeningDate(true),
m_includeUnusedAccounts(false),
m_forecastDone(false),
m_includeFutureTransactions(true),
m_includeScheduledTransactions(true)
{
setHistoryStartDate(QDate::currentDate().addDays(-forecastCycles()*accountsCycle()));
setHistoryEndDate(QDate::currentDate().addDays(-1));
}
void MyMoneyForecast::doForecast()
{
int fDays = calculateBeginForecastDay();
int fMethod = forecastMethod();
int fAccCycle = accountsCycle();
int fCycles = forecastCycles();
//validate settings
if (fAccCycle < 1
|| fCycles < 1
|| fDays < 1) {
throw MYMONEYEXCEPTION("Illegal settings when calling doForecast. Settings must be higher than 0");
}
//initialize global variables
setForecastDays(fDays);
setForecastStartDate(QDate::currentDate().addDays(1));
setForecastEndDate(QDate::currentDate().addDays(fDays));
setAccountsCycle(fAccCycle);
setForecastCycles(fCycles);
setHistoryStartDate(forecastCycles() * accountsCycle());
setHistoryEndDate(QDate::currentDate().addDays(-1)); //yesterday
//clear all data before calculating
m_accountListPast.clear();
m_accountList.clear();
m_accountTrendList.clear();
//set forecast accounts
setForecastAccountList();
switch (fMethod) {
case eScheduled:
doFutureScheduledForecast();
calculateScheduledDailyBalances();
break;
case eHistoric:
pastTransactions();
calculateHistoricDailyBalances();
break;
default:
break;
}
//flag the forecast as done
m_forecastDone = true;
}
MyMoneyForecast::~MyMoneyForecast()
{
}
void MyMoneyForecast::pastTransactions()
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyTransactionFilter filter;
filter.setDateFilter(historyStartDate(), historyEndDate());
filter.setReportAllSplits(false);
QList<MyMoneyTransaction> transactions = file->transactionList(filter);
QList<MyMoneyTransaction>::const_iterator it_t = transactions.constBegin();
//Check past transactions
for (; it_t != transactions.constEnd(); ++it_t) {
const QList<MyMoneySplit>& splits = (*it_t).splits();
QList<MyMoneySplit>::const_iterator it_s = splits.begin();
for (; it_s != splits.end(); ++it_s) {
if (!(*it_s).shares().isZero()) {
MyMoneyAccount acc = file->account((*it_s).accountId());
//workaround for stock accounts which have faulty opening dates
QDate openingDate;
- if (acc.accountType() == MyMoneyAccount::Stock) {
+ if (acc.accountType() == eMyMoney::Account::Stock) {
MyMoneyAccount parentAccount = file->account(acc.parentAccountId());
openingDate = parentAccount.openingDate();
} else {
openingDate = acc.openingDate();
}
if (isForecastAccount(acc) //If it is one of the accounts we are checking, add the amount of the transaction
&& ((openingDate < (*it_t).postDate() && skipOpeningDate())
|| !skipOpeningDate())) { //don't take the opening day of the account to calculate balance
dailyBalances balance;
//FIXME deal with leap years
balance = m_accountListPast[acc.id()];
- if (acc.accountType() == MyMoneyAccount::Income) {//if it is income, the balance is stored as negative number
+ if (acc.accountType() == eMyMoney::Account::Income) {//if it is income, the balance is stored as negative number
balance[(*it_t).postDate()] += ((*it_s).shares() * MyMoneyMoney::MINUS_ONE);
} else {
balance[(*it_t).postDate()] += (*it_s).shares();
}
// check if this is a new account for us
m_accountListPast[acc.id()] = balance;
}
}
}
}
//purge those accounts with no transactions on the period
if (isIncludingUnusedAccounts() == false)
purgeForecastAccountsList(m_accountListPast);
//calculate running sum
QSet<QString>::ConstIterator it_n;
for (it_n = m_forecastAccounts.begin(); it_n != m_forecastAccounts.end(); ++it_n) {
MyMoneyAccount acc = file->account(*it_n);
m_accountListPast[acc.id()][historyStartDate().addDays(-1)] = file->balance(acc.id(), historyStartDate().addDays(-1));
for (QDate it_date = historyStartDate(); it_date <= historyEndDate();) {
m_accountListPast[acc.id()][it_date] += m_accountListPast[acc.id()][it_date.addDays(-1)]; //Running sum
it_date = it_date.addDays(1);
}
}
//adjust value of investments to deep currency
for (it_n = m_forecastAccounts.begin(); it_n != m_forecastAccounts.end(); ++it_n) {
MyMoneyAccount acc = file->account(*it_n);
if (acc.isInvest()) {
//get the id of the security for that account
MyMoneySecurity undersecurity = file->security(acc.currencyId());
if (! undersecurity.isCurrency()) { //only do it if the security is not an actual currency
MyMoneyMoney rate = MyMoneyMoney::ONE; //set the default value
for (QDate it_date = historyStartDate().addDays(-1) ; it_date <= historyEndDate();) {
//get the price for the tradingCurrency that day
const MyMoneyPrice &price = file->price(undersecurity.id(), undersecurity.tradingCurrency(), it_date);
if (price.isValid()) {
rate = price.rate(undersecurity.tradingCurrency());
}
//value is the amount of shares multiplied by the rate of the deep currency
m_accountListPast[acc.id()][it_date] = m_accountListPast[acc.id()][it_date] * rate;
it_date = it_date.addDays(1);
}
}
}
}
}
bool MyMoneyForecast::isForecastAccount(const MyMoneyAccount& acc)
{
if (m_forecastAccounts.isEmpty()) {
setForecastAccountList();
}
return m_forecastAccounts.contains(acc.id());
}
void MyMoneyForecast::calculateAccountTrendList()
{
MyMoneyFile* file = MyMoneyFile::instance();
int auxForecastTerms;
int totalWeight = 0;
//Calculate account trends
QSet<QString>::ConstIterator it_n;
for (it_n = m_forecastAccounts.begin(); it_n != m_forecastAccounts.end(); ++it_n) {
MyMoneyAccount acc = file->account(*it_n);
m_accountTrendList[acc.id()][0] = MyMoneyMoney(); // for today, the trend is 0
auxForecastTerms = forecastCycles();
if (skipOpeningDate()) {
QDate openingDate;
- if (acc.accountType() == MyMoneyAccount::Stock) {
+ if (acc.accountType() == eMyMoney::Account::Stock) {
MyMoneyAccount parentAccount = file->account(acc.parentAccountId());
openingDate = parentAccount.openingDate();
} else {
openingDate = acc.openingDate();
}
if (openingDate > historyStartDate()) { //if acc opened after forecast period
auxForecastTerms = 1 + ((openingDate.daysTo(historyEndDate()) + 1) / accountsCycle()); // set forecastTerms to a lower value, to calculate only based on how long this account was opened
}
}
switch (historyMethod()) {
//moving average
case 0: {
for (int t_day = 1; t_day <= accountsCycle(); t_day++)
m_accountTrendList[acc.id()][t_day] = accountMovingAverage(acc, t_day, auxForecastTerms); //moving average
break;
}
//weighted moving average
case 1: {
//calculate total weight for moving average
if (auxForecastTerms == forecastCycles()) {
totalWeight = (auxForecastTerms * (auxForecastTerms + 1)) / 2; //totalWeight is the triangular number of auxForecastTerms
} else {
//if only taking a few periods, totalWeight is the sum of the weight for most recent periods
for (int i = 1, w = forecastCycles(); i <= auxForecastTerms; ++i, --w)
totalWeight += w;
}
for (int t_day = 1; t_day <= accountsCycle(); t_day++)
m_accountTrendList[acc.id()][t_day] = accountWeightedMovingAverage(acc, t_day, totalWeight);
break;
}
case 2: {
//calculate mean term
MyMoneyMoney meanTerms = MyMoneyMoney((auxForecastTerms * (auxForecastTerms + 1)) / 2, 1) / MyMoneyMoney(auxForecastTerms, 1);
for (int t_day = 1; t_day <= accountsCycle(); t_day++)
m_accountTrendList[acc.id()][t_day] = accountLinearRegression(acc, t_day, auxForecastTerms, meanTerms);
break;
}
default:
break;
}
}
}
QList<MyMoneyAccount> MyMoneyForecast::forecastAccountList()
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyAccount> accList;
//Get all accounts from the file and check if they are of the right type to calculate forecast
file->accountList(accList);
QList<MyMoneyAccount>::iterator accList_t = accList.begin();
for (; accList_t != accList.end();) {
MyMoneyAccount acc = *accList_t;
if (acc.isClosed() //check the account is not closed
|| (!acc.isAssetLiability())) {
- //|| (acc.accountType() == MyMoneyAccount::Investment) ) {//check that it is not an Investment account and only include Stock accounts
+ //|| (acc.accountType() == eMyMoney::Account::Investment) ) {//check that it is not an Investment account and only include Stock accounts
//remove the account if it is not of the correct type
accList_t = accList.erase(accList_t);
} else {
++accList_t;
}
}
return accList;
}
QList<MyMoneyAccount> MyMoneyForecast::accountList()
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyAccount> accList;
QStringList emptyStringList;
//Get all accounts from the file and check if they are present
file->accountList(accList, emptyStringList, false);
QList<MyMoneyAccount>::iterator accList_t = accList.begin();
for (; accList_t != accList.end();) {
MyMoneyAccount acc = *accList_t;
if (!isForecastAccount(acc)) {
accList_t = accList.erase(accList_t); //remove the account
} else {
++accList_t;
}
}
return accList;
}
MyMoneyMoney MyMoneyForecast::calculateAccountTrend(const MyMoneyAccount& acc, int trendDays)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyTransactionFilter filter;
MyMoneyMoney netIncome;
QDate startDate;
QDate openingDate = acc.openingDate();
//validate arguments
if (trendDays < 1) {
throw MYMONEYEXCEPTION("Illegal arguments when calling calculateAccountTrend. trendDays must be higher than 0");
}
//If it is a new account, we don't take into account the first day
//because it is usually a weird one and it would mess up the trend
if (openingDate.daysTo(QDate::currentDate()) < trendDays) {
startDate = (acc.openingDate()).addDays(1);
} else {
startDate = QDate::currentDate().addDays(-trendDays);
}
//get all transactions for the period
filter.setDateFilter(startDate, QDate::currentDate());
- if (acc.accountGroup() == MyMoneyAccount::Income
- || acc.accountGroup() == MyMoneyAccount::Expense) {
+ if (acc.accountGroup() == eMyMoney::Account::Income
+ || acc.accountGroup() == eMyMoney::Account::Expense) {
filter.addCategory(acc.id());
} else {
filter.addAccount(acc.id());
}
filter.setReportAllSplits(false);
QList<MyMoneyTransaction> transactions = file->transactionList(filter);
QList<MyMoneyTransaction>::const_iterator it_t = transactions.constBegin();
//add all transactions for that account
for (; it_t != transactions.constEnd(); ++it_t) {
const QList<MyMoneySplit>& splits = (*it_t).splits();
QList<MyMoneySplit>::const_iterator it_s = splits.begin();
for (; it_s != splits.end(); ++it_s) {
if (!(*it_s).shares().isZero()) {
if (acc.id() == (*it_s).accountId()) netIncome += (*it_s).value();
}
}
}
//calculate trend of the account in the past period
MyMoneyMoney accTrend;
//don't take into account the first day of the account
if (openingDate.daysTo(QDate::currentDate()) < trendDays) {
accTrend = netIncome / MyMoneyMoney(openingDate.daysTo(QDate::currentDate()) - 1, 1);
} else {
accTrend = netIncome / MyMoneyMoney(trendDays, 1);
}
return accTrend;
}
MyMoneyMoney MyMoneyForecast::accountMovingAverage(const MyMoneyAccount &acc, const int trendDay, const int forecastTerms)
{
//Calculate a daily trend for the account based on the accounts of a given number of terms
//With a term of 1 month and 3 terms, it calculates the trend taking the transactions occurred at that day and the day before,
//for the last 3 months
MyMoneyMoney balanceVariation;
for (int it_terms = 0; (trendDay + (accountsCycle()*it_terms)) <= historyDays(); ++it_terms) { //sum for each term
MyMoneyMoney balanceBefore = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-2)]; //get balance for the day before
MyMoneyMoney balanceAfter = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
balanceVariation += (balanceAfter - balanceBefore); //add the balance variation between days
}
//calculate average of the variations
return (balanceVariation / MyMoneyMoney(forecastTerms, 1)).convert(10000);
}
MyMoneyMoney MyMoneyForecast::accountWeightedMovingAverage(const MyMoneyAccount &acc, const int trendDay, const int totalWeight)
{
MyMoneyMoney balanceVariation;
for (int it_terms = 0, weight = 1; (trendDay + (accountsCycle()*it_terms)) <= historyDays(); ++it_terms, ++weight) { //sum for each term multiplied by weight
MyMoneyMoney balanceBefore = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-2)]; //get balance for the day before
MyMoneyMoney balanceAfter = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
balanceVariation += ((balanceAfter - balanceBefore) * MyMoneyMoney(weight, 1)); //add the balance variation between days multiplied by its weight
}
//calculate average of the variations
return (balanceVariation / MyMoneyMoney(totalWeight, 1)).convert(10000);
}
MyMoneyMoney MyMoneyForecast::accountLinearRegression(const MyMoneyAccount &acc, const int trendDay, const int actualTerms, const MyMoneyMoney& meanTerms)
{
MyMoneyMoney meanBalance, totalBalance, totalTerms;
totalTerms = MyMoneyMoney(actualTerms, 1);
//calculate mean balance
for (int it_terms = forecastCycles() - actualTerms; (trendDay + (accountsCycle()*it_terms)) <= historyDays(); ++it_terms) { //sum for each term
totalBalance += m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
}
meanBalance = totalBalance / MyMoneyMoney(actualTerms, 1);
meanBalance = meanBalance.convert(10000);
//calculate b1
//first calculate x - mean x multiplied by y - mean y
MyMoneyMoney totalXY, totalSqX;
for (int it_terms = forecastCycles() - actualTerms, term = 1; (trendDay + (accountsCycle()*it_terms)) <= historyDays(); ++it_terms, ++term) { //sum for each term
MyMoneyMoney balance = m_accountListPast[acc.id()][historyStartDate().addDays(trendDay+(accountsCycle()*it_terms)-1)];
MyMoneyMoney balMeanBal = balance - meanBalance;
MyMoneyMoney termMeanTerm = (MyMoneyMoney(term, 1) - meanTerms);
totalXY += (balMeanBal * termMeanTerm).convert(10000);
totalSqX += (termMeanTerm * termMeanTerm).convert(10000);
}
totalXY = (totalXY / MyMoneyMoney(actualTerms, 1)).convert(10000);
totalSqX = (totalSqX / MyMoneyMoney(actualTerms, 1)).convert(10000);
//check zero
if (totalSqX.isZero())
return MyMoneyMoney();
MyMoneyMoney linReg = (totalXY / totalSqX).convert(10000);
return linReg;
}
void MyMoneyForecast::calculateHistoricDailyBalances()
{
MyMoneyFile* file = MyMoneyFile::instance();
calculateAccountTrendList();
//Calculate account daily balances
QSet<QString>::ConstIterator it_n;
for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
MyMoneyAccount acc = file->account(*it_n);
//set the starting balance of the account
setStartingBalance(acc);
switch (historyMethod()) {
case 0:
case 1: {
for (QDate f_day = forecastStartDate(); f_day <= forecastEndDate();) {
for (int t_day = 1; t_day <= accountsCycle(); ++t_day) {
MyMoneyMoney balanceDayBefore = m_accountList[acc.id()][(f_day.addDays(-1))];//balance of the day before
MyMoneyMoney accountDailyTrend = m_accountTrendList[acc.id()][t_day]; //trend for that day
//balance of the day is the balance of the day before multiplied by the trend for the day
m_accountList[acc.id()][f_day] = balanceDayBefore;
m_accountList[acc.id()][f_day] += accountDailyTrend; //movement trend for that particular day
m_accountList[acc.id()][f_day] = m_accountList[acc.id()][f_day].convert(acc.fraction());
//m_accountList[acc.id()][f_day] += m_accountListPast[acc.id()][f_day.addDays(-historyDays())];
f_day = f_day.addDays(1);
}
}
}
break;
case 2: {
QDate baseDate = QDate::currentDate().addDays(-accountsCycle());
for (int t_day = 1; t_day <= accountsCycle(); ++t_day) {
int f_day = 1;
QDate fDate = baseDate.addDays(accountsCycle() + 1);
while (fDate <= forecastEndDate()) {
//the calculation is based on the balance for the last month, that is then multiplied by the trend
m_accountList[acc.id()][fDate] = m_accountListPast[acc.id()][baseDate] + (m_accountTrendList[acc.id()][t_day] * MyMoneyMoney(f_day, 1));
m_accountList[acc.id()][fDate] = m_accountList[acc.id()][fDate].convert(acc.fraction());
++f_day;
fDate = baseDate.addDays(accountsCycle() * f_day);
}
baseDate = baseDate.addDays(1);
}
}
}
}
}
MyMoneyMoney MyMoneyForecast::forecastBalance(const MyMoneyAccount& acc, const QDate &forecastDate)
{
dailyBalances balance;
MyMoneyMoney MM_amount = MyMoneyMoney();
//Check if acc is not a forecast account, return 0
if (!isForecastAccount(acc)) {
return MM_amount;
}
if (m_accountList.contains(acc.id())) {
balance = m_accountList.value(acc.id());
}
if (balance.contains(forecastDate)) { //if the date is not in the forecast, it returns 0
MM_amount = balance.value(forecastDate);
}
return MM_amount;
}
/**
* Returns the forecast balance trend for account @a acc for offset @p int
* offset is days from current date, inside forecast days.
* Returns 0 if offset not in range of forecast days.
*/
MyMoneyMoney MyMoneyForecast::forecastBalance(const MyMoneyAccount& acc, int offset)
{
QDate forecastDate = QDate::currentDate().addDays(offset);
return forecastBalance(acc, forecastDate);
}
void MyMoneyForecast::doFutureScheduledForecast()
{
MyMoneyFile* file = MyMoneyFile::instance();
if (isIncludingFutureTransactions())
addFutureTransactions();
if (isIncludingScheduledTransactions())
addScheduledTransactions();
//do not show accounts with no transactions
if (!isIncludingUnusedAccounts())
purgeForecastAccountsList(m_accountList);
//adjust value of investments to deep currency
QSet<QString>::ConstIterator it_n;
for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
MyMoneyAccount acc = file->account(*it_n);
if (acc.isInvest()) {
//get the id of the security for that account
MyMoneySecurity undersecurity = file->security(acc.currencyId());
//only do it if the security is not an actual currency
if (! undersecurity.isCurrency()) {
//set the default value
MyMoneyMoney rate = MyMoneyMoney::ONE;
for (QDate it_day = QDate::currentDate(); it_day <= forecastEndDate();) {
//get the price for the tradingCurrency that day
const MyMoneyPrice &price = file->price(undersecurity.id(), undersecurity.tradingCurrency(), it_day);
if (price.isValid()) {
rate = price.rate(undersecurity.tradingCurrency());
}
//value is the amount of shares multiplied by the rate of the deep currency
m_accountList[acc.id()][it_day] = m_accountList[acc.id()][it_day] * rate;
it_day = it_day.addDays(1);
}
}
}
}
}
void MyMoneyForecast::addFutureTransactions()
{
MyMoneyTransactionFilter filter;
MyMoneyFile* file = MyMoneyFile::instance();
// collect and process all transactions that have already been entered but
// are located in the future.
filter.setDateFilter(forecastStartDate(), forecastEndDate());
filter.setReportAllSplits(false);
QList<MyMoneyTransaction> transactions = file->transactionList(filter);
QList<MyMoneyTransaction>::const_iterator it_t = transactions.constBegin();
for (; it_t != transactions.constEnd(); ++it_t) {
const QList<MyMoneySplit>& splits = (*it_t).splits();
QList<MyMoneySplit>::const_iterator it_s = splits.begin();
for (; it_s != splits.end(); ++it_s) {
if (!(*it_s).shares().isZero()) {
MyMoneyAccount acc = file->account((*it_s).accountId());
if (isForecastAccount(acc)) {
dailyBalances balance;
balance = m_accountList[acc.id()];
//if it is income, the balance is stored as negative number
- if (acc.accountType() == MyMoneyAccount::Income) {
+ if (acc.accountType() == eMyMoney::Account::Income) {
balance[(*it_t).postDate()] += ((*it_s).shares() * MyMoneyMoney::MINUS_ONE);
} else {
balance[(*it_t).postDate()] += (*it_s).shares();
}
m_accountList[acc.id()] = balance;
}
}
}
}
#if 0
QFile trcFile("forecast.csv");
trcFile.open(QIODevice::WriteOnly);
QTextStream s(&trcFile);
{
s << "Already present transactions\n";
QMap<QString, dailyBalances>::Iterator it_a;
QSet<QString>::ConstIterator it_n;
for (it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
MyMoneyAccount acc = file->account(*it_n);
it_a = m_accountList.find(*it_n);
s << "\"" << acc.name() << "\",";
for (int i = 0; i < 90; ++i) {
s << "\"" << (*it_a)[i].formatMoney("") << "\",";
}
s << "\n";
}
}
#endif
}
void MyMoneyForecast::addScheduledTransactions()
{
MyMoneyFile* file = MyMoneyFile::instance();
// now process all the schedules that may have an impact
QList<MyMoneySchedule> schedule;
- schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY,
- QDate(), forecastEndDate());
+ schedule = file->scheduleList(QString(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any,
+ QDate(), forecastEndDate(), false);
if (schedule.count() > 0) {
QList<MyMoneySchedule>::Iterator it;
do {
qSort(schedule);
it = schedule.begin();
if (it == schedule.end())
break;
if ((*it).isFinished()) {
schedule.erase(it);
continue;
}
QDate date = (*it).nextPayment((*it).lastPayment());
if (!date.isValid()) {
schedule.erase(it);
continue;
}
QDate nextDate =
(*it).adjustedNextPayment((*it).adjustedDate((*it).lastPayment(),
(*it).weekendOption()));
if (nextDate > forecastEndDate()) {
// We're done with this schedule, let's move on to the next
schedule.erase(it);
continue;
}
// found the next schedule. process it
MyMoneyAccount acc = (*it).account();
if (!acc.id().isEmpty()) {
try {
- if (acc.accountType() != MyMoneyAccount::Investment) {
+ if (acc.accountType() != eMyMoney::Account::Investment) {
MyMoneyTransaction t = (*it).transaction();
// only process the entry, if it is still active
if (!(*it).isFinished() && nextDate != QDate()) {
// make sure we have all 'starting balances' so that the autocalc works
QList<MyMoneySplit>::const_iterator it_s;
QMap<QString, MyMoneyMoney> balanceMap;
for (it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) {
MyMoneyAccount acc = file->account((*it_s).accountId());
if (isForecastAccount(acc)) {
// collect all overdues on the first day
QDate forecastDate = nextDate;
if (QDate::currentDate() >= nextDate)
forecastDate = QDate::currentDate().addDays(1);
dailyBalances balance;
balance = m_accountList[acc.id()];
for (QDate f_day = QDate::currentDate(); f_day < forecastDate;) {
balanceMap[acc.id()] += m_accountList[acc.id()][f_day];
f_day = f_day.addDays(1);
}
}
}
// take care of the autoCalc stuff
calculateAutoLoan(*it, t, balanceMap);
// now add the splits to the balances
for (it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) {
MyMoneyAccount acc = file->account((*it_s).accountId());
if (isForecastAccount(acc)) {
dailyBalances balance;
balance = m_accountList[acc.id()];
//int offset = QDate::currentDate().daysTo(nextDate);
//if(offset <= 0) { // collect all overdues on the first day
// offset = 1;
//}
// collect all overdues on the first day
QDate forecastDate = nextDate;
if (QDate::currentDate() >= nextDate)
forecastDate = QDate::currentDate().addDays(1);
- if (acc.accountType() == MyMoneyAccount::Income) {
+ if (acc.accountType() == eMyMoney::Account::Income) {
balance[forecastDate] += ((*it_s).shares() * MyMoneyMoney::MINUS_ONE);
} else {
balance[forecastDate] += (*it_s).shares();
}
m_accountList[acc.id()] = balance;
}
}
}
}
(*it).setLastPayment(date);
} catch (const MyMoneyException &e) {
qDebug() << Q_FUNC_INFO << " Schedule " << (*it).id() << " (" << (*it).name() << "): " << e.what();
schedule.erase(it);
}
} else {
// remove schedule from list
schedule.erase(it);
}
} while (1);
}
#if 0
{
s << "\n\nAdded scheduled transactions\n";
QMap<QString, dailyBalances>::Iterator it_a;
QSet<QString>::ConstIterator it_n;
for (it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
MyMoneyAccount acc = file->account(*it_n);
it_a = m_accountList.find(*it_n);
s << "\"" << acc.name() << "\",";
for (int i = 0; i < 90; ++i) {
s << "\"" << (*it_a)[i].formatMoney("") << "\",";
}
s << "\n";
}
}
#endif
}
void MyMoneyForecast::calculateScheduledDailyBalances()
{
MyMoneyFile* file = MyMoneyFile::instance();
//Calculate account daily balances
QSet<QString>::ConstIterator it_n;
for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
MyMoneyAccount acc = file->account(*it_n);
//set the starting balance of the account
setStartingBalance(acc);
for (QDate f_day = forecastStartDate(); f_day <= forecastEndDate();) {
MyMoneyMoney balanceDayBefore = m_accountList[acc.id()][(f_day.addDays(-1))];//balance of the day before
m_accountList[acc.id()][f_day] += balanceDayBefore; //running sum
f_day = f_day.addDays(1);
}
}
}
int MyMoneyForecast::daysToMinimumBalance(const MyMoneyAccount& acc)
{
QString minimumBalance = acc.value("minBalanceAbsolute");
MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance);
dailyBalances balance;
//Check if acc is not a forecast account, return -1
if (!isForecastAccount(acc)) {
return -1;
}
balance = m_accountList[acc.id()];
for (QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate();) {
if (minBalance > balance[it_day]) {
return QDate::currentDate().daysTo(it_day);
}
it_day = it_day.addDays(1);
}
return -1;
}
int MyMoneyForecast::daysToZeroBalance(const MyMoneyAccount& acc)
{
dailyBalances balance;
//Check if acc is not a forecast account, return -1
if (!isForecastAccount(acc)) {
return -2;
}
balance = m_accountList[acc.id()];
- if (acc.accountGroup() == MyMoneyAccount::Asset) {
+ if (acc.accountGroup() == eMyMoney::Account::Asset) {
for (QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate();) {
if (balance[it_day] < MyMoneyMoney()) {
return QDate::currentDate().daysTo(it_day);
}
it_day = it_day.addDays(1);
}
- } else if (acc.accountGroup() == MyMoneyAccount::Liability) {
+ } else if (acc.accountGroup() == eMyMoney::Account::Liability) {
for (QDate it_day = QDate::currentDate() ; it_day <= forecastEndDate();) {
if (balance[it_day] > MyMoneyMoney()) {
return QDate::currentDate().daysTo(it_day);
}
it_day = it_day.addDays(1);
}
}
return -1;
}
void MyMoneyForecast::setForecastAccountList()
{
//get forecast accounts
QList<MyMoneyAccount> accList;
accList = forecastAccountList();
QList<MyMoneyAccount>::const_iterator accList_t = accList.constBegin();
for (; accList_t != accList.constEnd(); ++accList_t) {
m_forecastAccounts.insert((*accList_t).id());
}
}
MyMoneyMoney MyMoneyForecast::accountCycleVariation(const MyMoneyAccount& acc)
{
MyMoneyMoney cycleVariation;
if (forecastMethod() == eHistoric) {
switch (historyMethod()) {
case 0:
case 1: {
for (int t_day = 1; t_day <= accountsCycle() ; ++t_day) {
cycleVariation += m_accountTrendList[acc.id()][t_day];
}
}
break;
case 2: {
cycleVariation = m_accountList[acc.id()][QDate::currentDate().addDays(accountsCycle())] - m_accountList[acc.id()][QDate::currentDate()];
break;
}
}
}
return cycleVariation;
}
MyMoneyMoney MyMoneyForecast::accountTotalVariation(const MyMoneyAccount& acc)
{
MyMoneyMoney totalVariation;
totalVariation = forecastBalance(acc, forecastEndDate()) - forecastBalance(acc, QDate::currentDate());
return totalVariation;
}
QList<QDate> MyMoneyForecast::accountMinimumBalanceDateList(const MyMoneyAccount& acc)
{
QList<QDate> minBalanceList;
int daysToBeginDay;
daysToBeginDay = QDate::currentDate().daysTo(beginForecastDate());
for (int t_cycle = 0; ((t_cycle * accountsCycle()) + daysToBeginDay) < forecastDays() ; ++t_cycle) {
MyMoneyMoney minBalance = forecastBalance(acc, (t_cycle * accountsCycle() + daysToBeginDay));
QDate minDate = QDate::currentDate().addDays(t_cycle * accountsCycle() + daysToBeginDay);
for (int t_day = 1; t_day <= accountsCycle() ; ++t_day) {
if (minBalance > forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day)) {
minBalance = forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day);
minDate = QDate::currentDate().addDays((t_cycle * accountsCycle()) + daysToBeginDay + t_day);
}
}
minBalanceList.append(minDate);
}
return minBalanceList;
}
QList<QDate> MyMoneyForecast::accountMaximumBalanceDateList(const MyMoneyAccount& acc)
{
QList<QDate> maxBalanceList;
int daysToBeginDay;
daysToBeginDay = QDate::currentDate().daysTo(beginForecastDate());
for (int t_cycle = 0; ((t_cycle * accountsCycle()) + daysToBeginDay) < forecastDays() ; ++t_cycle) {
MyMoneyMoney maxBalance = forecastBalance(acc, ((t_cycle * accountsCycle()) + daysToBeginDay));
QDate maxDate = QDate::currentDate().addDays((t_cycle * accountsCycle()) + daysToBeginDay);
for (int t_day = 0; t_day < accountsCycle() ; ++t_day) {
if (maxBalance < forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day)) {
maxBalance = forecastBalance(acc, (t_cycle * accountsCycle()) + daysToBeginDay + t_day);
maxDate = QDate::currentDate().addDays((t_cycle * accountsCycle()) + daysToBeginDay + t_day);
}
}
maxBalanceList.append(maxDate);
}
return maxBalanceList;
}
MyMoneyMoney MyMoneyForecast::accountAverageBalance(const MyMoneyAccount& acc)
{
MyMoneyMoney totalBalance;
for (int f_day = 1; f_day <= forecastDays() ; ++f_day) {
totalBalance += forecastBalance(acc, f_day);
}
return totalBalance / MyMoneyMoney(forecastDays(), 1);
}
int MyMoneyForecast::calculateBeginForecastDay()
{
int fDays = forecastDays();
int beginDay = beginForecastDay();
int accCycle = accountsCycle();
QDate beginDate;
//if 0, beginDate is current date and forecastDays remains unchanged
if (beginDay == 0) {
setBeginForecastDate(QDate::currentDate());
return fDays;
}
//adjust if beginDay more than days of current month
if (QDate::currentDate().daysInMonth() < beginDay)
beginDay = QDate::currentDate().daysInMonth();
//if beginDay still to come, calculate and return
if (QDate::currentDate().day() <= beginDay) {
beginDate = QDate(QDate::currentDate().year(), QDate::currentDate().month(), beginDay);
fDays += QDate::currentDate().daysTo(beginDate);
setBeginForecastDate(beginDate);
return fDays;
}
//adjust beginDay for next month
if (QDate::currentDate().addMonths(1).daysInMonth() < beginDay)
beginDay = QDate::currentDate().addMonths(1).daysInMonth();
//if beginDay of next month comes before 1 interval, use beginDay next month
if (QDate::currentDate().addDays(accCycle) >=
(QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1).addDays(beginDay - 1))) {
beginDate = QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1).addDays(beginDay - 1);
fDays += QDate::currentDate().daysTo(beginDate);
} else { //add intervals to current beginDay and take the first after current date
beginDay = ((((QDate::currentDate().day() - beginDay) / accCycle) + 1) * accCycle) + beginDay;
beginDate = QDate::currentDate().addDays(beginDay - QDate::currentDate().day());
fDays += QDate::currentDate().daysTo(beginDate);
}
setBeginForecastDate(beginDate);
return fDays;
}
void MyMoneyForecast::purgeForecastAccountsList(QMap<QString, dailyBalances>& accountList)
{
m_forecastAccounts.intersect(accountList.keys().toSet());
}
void MyMoneyForecast::createBudget(MyMoneyBudget& budget, QDate historyStart, QDate historyEnd, QDate budgetStart, QDate budgetEnd, const bool returnBudget)
{
// clear all data except the id and name
QString name = budget.name();
budget = MyMoneyBudget(budget.id(), MyMoneyBudget());
budget.setName(name);
//check parameters
if (historyStart > historyEnd ||
budgetStart > budgetEnd ||
budgetStart <= historyEnd) {
throw MYMONEYEXCEPTION("Illegal parameters when trying to create budget");
}
//get forecast method
int fMethod = forecastMethod();
//set start date to 1st of month and end dates to last day of month, since we deal with full months in budget
historyStart = QDate(historyStart.year(), historyStart.month(), 1);
historyEnd = QDate(historyEnd.year(), historyEnd.month(), historyEnd.daysInMonth());
budgetStart = QDate(budgetStart.year(), budgetStart.month(), 1);
budgetEnd = QDate(budgetEnd.year(), budgetEnd.month(), budgetEnd.daysInMonth());
//set forecast parameters
setHistoryStartDate(historyStart);
setHistoryEndDate(historyEnd);
setForecastStartDate(budgetStart);
setForecastEndDate(budgetEnd);
setForecastDays(budgetStart.daysTo(budgetEnd) + 1);
if (budgetStart.daysTo(budgetEnd) > historyStart.daysTo(historyEnd)) { //if history period is shorter than budget, use that one as the trend length
setAccountsCycle(historyStart.daysTo(historyEnd)); //we set the accountsCycle to the base timeframe we will use to calculate the average (eg. 180 days, 365, etc)
} else { //if one timeframe is larger than the other, but not enough to be 1 time larger, we take the lowest value
setAccountsCycle(budgetStart.daysTo(budgetEnd));
}
setForecastCycles((historyStart.daysTo(historyEnd) / accountsCycle()));
if (forecastCycles() == 0) //the cycles must be at least 1
setForecastCycles(1);
//do not skip opening date
setSkipOpeningDate(false);
//clear and set accounts list we are going to use. Categories, in this case
m_forecastAccounts.clear();
setBudgetAccountList();
//calculate budget according to forecast method
switch (fMethod) {
case eScheduled:
doFutureScheduledForecast();
calculateScheduledMonthlyBalances();
break;
case eHistoric:
pastTransactions(); //get all transactions for history period
calculateAccountTrendList();
calculateHistoricMonthlyBalances(); //add all balances of each month and put at the 1st day of each month
break;
default:
break;
}
//flag the forecast as done
m_forecastDone = true;
//only fill the budget if it is going to be used
if (returnBudget) {
//setup the budget itself
MyMoneyFile* file = MyMoneyFile::instance();
budget.setBudgetStart(budgetStart);
//go through all the accounts and add them to budget
QSet<QString>::ConstIterator it_nc;
for (it_nc = m_forecastAccounts.constBegin(); it_nc != m_forecastAccounts.constEnd(); ++it_nc) {
MyMoneyAccount acc = file->account(*it_nc);
MyMoneyBudget::AccountGroup budgetAcc;
budgetAcc.setId(acc.id());
budgetAcc.setBudgetLevel(MyMoneyBudget::AccountGroup::eMonthByMonth);
for (QDate f_date = forecastStartDate(); f_date <= forecastEndDate();) {
MyMoneyBudget::PeriodGroup period;
//add period to budget account
period.setStartDate(f_date);
period.setAmount(forecastBalance(acc, f_date));
budgetAcc.addPeriod(f_date, period);
//next month
f_date = f_date.addMonths(1);
}
//add budget account to budget
budget.setAccount(budgetAcc, acc.id());
}
}
}
void MyMoneyForecast::setBudgetAccountList()
{
//get budget accounts
QList<MyMoneyAccount> accList;
accList = budgetAccountList();
QList<MyMoneyAccount>::const_iterator accList_t = accList.constBegin();
for (; accList_t != accList.constEnd(); ++accList_t) {
m_forecastAccounts.insert((*accList_t).id());
}
}
QList<MyMoneyAccount> MyMoneyForecast::budgetAccountList()
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyAccount> accList;
QStringList emptyStringList;
//Get all accounts from the file and check if they are of the right type to calculate forecast
file->accountList(accList, emptyStringList, false);
QList<MyMoneyAccount>::iterator accList_t = accList.begin();
for (; accList_t != accList.end();) {
MyMoneyAccount acc = *accList_t;
if (acc.isClosed() //check the account is not closed
|| (!acc.isIncomeExpense())) {
//remove the account if it is not of the correct type
accList_t = accList.erase(accList_t);
} else {
++accList_t;
}
}
return accList;
}
void MyMoneyForecast::calculateHistoricMonthlyBalances()
{
MyMoneyFile* file = MyMoneyFile::instance();
//Calculate account monthly balances
QSet<QString>::ConstIterator it_n;
for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
MyMoneyAccount acc = file->account(*it_n);
for (QDate f_date = forecastStartDate(); f_date <= forecastEndDate();) {
for (int f_day = 1; f_day <= accountsCycle() && f_date <= forecastEndDate(); ++f_day) {
MyMoneyMoney accountDailyTrend = m_accountTrendList[acc.id()][f_day]; //trend for that day
//check for leap year
if (f_date.month() == 2 && f_date.day() == 29)
f_date = f_date.addDays(1); //skip 1 day
m_accountList[acc.id()][QDate(f_date.year(), f_date.month(), 1)] += accountDailyTrend; //movement trend for that particular day
f_date = f_date.addDays(1);
}
}
}
}
void MyMoneyForecast::calculateScheduledMonthlyBalances()
{
MyMoneyFile* file = MyMoneyFile::instance();
//Calculate account monthly balances
QSet<QString>::ConstIterator it_n;
for (it_n = m_forecastAccounts.constBegin(); it_n != m_forecastAccounts.constEnd(); ++it_n) {
MyMoneyAccount acc = file->account(*it_n);
for (QDate f_date = forecastStartDate(); f_date <= forecastEndDate(); f_date = f_date.addDays(1)) {
//get the trend for the day
MyMoneyMoney accountDailyBalance = m_accountList[acc.id()][f_date];
//do not add if it is the beginning of the month
//otherwise we end up with duplicated values as reported by Marko Käning
if (f_date != QDate(f_date.year(), f_date.month(), 1))
m_accountList[acc.id()][QDate(f_date.year(), f_date.month(), 1)] += accountDailyBalance;
}
}
}
void MyMoneyForecast::setStartingBalance(const MyMoneyAccount &acc)
{
MyMoneyFile* file = MyMoneyFile::instance();
//Get current account balance
if (acc.isInvest()) { //investments require special treatment
//get the security id of that account
MyMoneySecurity undersecurity = file->security(acc.currencyId());
//only do it if the security is not an actual currency
if (! undersecurity.isCurrency()) {
//set the default value
MyMoneyMoney rate = MyMoneyMoney::ONE;
//get te
const MyMoneyPrice &price = file->price(undersecurity.id(), undersecurity.tradingCurrency(), QDate::currentDate());
if (price.isValid()) {
rate = price.rate(undersecurity.tradingCurrency());
}
m_accountList[acc.id()][QDate::currentDate()] = file->balance(acc.id(), QDate::currentDate()) * rate;
}
} else {
m_accountList[acc.id()][QDate::currentDate()] = file->balance(acc.id(), QDate::currentDate());
}
//if the method is linear regression, we have to add the opening balance to m_accountListPast
if (forecastMethod() == eHistoric && historyMethod() == 2) {
//FIXME workaround for stock opening dates
QDate openingDate;
- if (acc.accountType() == MyMoneyAccount::Stock) {
+ if (acc.accountType() == eMyMoney::Account::Stock) {
MyMoneyAccount parentAccount = file->account(acc.parentAccountId());
openingDate = parentAccount.openingDate();
} else {
openingDate = acc.openingDate();
}
//add opening balance only if it opened after the history start
if (openingDate >= historyStartDate()) {
MyMoneyMoney openingBalance;
openingBalance = file->balance(acc.id(), openingDate);
//calculate running sum
for (QDate it_date = openingDate; it_date <= historyEndDate(); it_date = it_date.addDays(1)) {
//investments require special treatment
if (acc.isInvest()) {
//get the security id of that account
MyMoneySecurity undersecurity = file->security(acc.currencyId());
//only do it if the security is not an actual currency
if (! undersecurity.isCurrency()) {
//set the default value
MyMoneyMoney rate = MyMoneyMoney::ONE;
//get the rate for that specific date
const MyMoneyPrice &price = file->price(undersecurity.id(), undersecurity.tradingCurrency(), it_date);
if (price.isValid()) {
rate = price.rate(undersecurity.tradingCurrency());
}
m_accountListPast[acc.id()][it_date] += openingBalance * rate;
}
} else {
m_accountListPast[acc.id()][it_date] += openingBalance;
}
}
}
}
}
void MyMoneyForecast::calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap<QString, MyMoneyMoney>& balances)
{
- if (schedule.type() == MyMoneySchedule::TYPE_LOANPAYMENT) {
+ if (schedule.type() == eMyMoney::Schedule::Type::LoanPayment) {
//get amortization and interest autoCalc splits
MyMoneySplit amortizationSplit = transaction.amortizationSplit();
MyMoneySplit interestSplit = transaction.interestSplit();
const bool interestSplitValid = !interestSplit.id().isEmpty();
if (!amortizationSplit.id().isEmpty()) {
MyMoneyAccountLoan acc(MyMoneyFile::instance()->account(amortizationSplit.accountId()));
MyMoneyFinancialCalculator calc;
QDate dueDate;
// FIXME: setup dueDate according to when the interest should be calculated
// current implementation: take the date of the next payment according to
// the schedule. If the calculation is based on the payment reception, and
// the payment is overdue then take the current date
dueDate = schedule.nextDueDate();
if (acc.interestCalculation() == MyMoneyAccountLoan::paymentReceived) {
if (dueDate < QDate::currentDate())
dueDate = QDate::currentDate();
}
// we need to calculate the balance at the time the payment is due
MyMoneyMoney balance;
if (balances.count() == 0)
balance = MyMoneyFile::instance()->balance(acc.id(), dueDate.addDays(-1));
else
balance = balances[acc.id()];
// FIXME: for now, we only support interest calculation at the end of the period
calc.setBep();
// FIXME: for now, we only support periodic compounding
calc.setDisc();
calc.setPF(MyMoneySchedule::eventsPerYear(schedule.occurrence()));
- MyMoneySchedule::occurrenceE compoundingOccurrence = static_cast<MyMoneySchedule::occurrenceE>(acc.interestCompounding());
- if (compoundingOccurrence == MyMoneySchedule::OCCUR_ANY)
+ eMyMoney::Schedule::Occurrence compoundingOccurrence = static_cast<eMyMoney::Schedule::Occurrence>(acc.interestCompounding());
+ if (compoundingOccurrence == eMyMoney::Schedule::Occurrence::Any)
compoundingOccurrence = schedule.occurrence();
calc.setCF(MyMoneySchedule::eventsPerYear(compoundingOccurrence));
calc.setPv(balance.toDouble());
calc.setIr(acc.interestRate(dueDate).abs().toDouble());
calc.setPmt(acc.periodicPayment().toDouble());
MyMoneyMoney interest(calc.interestDue(), 100), amortization;
interest = interest.abs(); // make sure it's positive for now
amortization = acc.periodicPayment() - interest;
- if (acc.accountType() == MyMoneyAccount::AssetLoan) {
+ if (acc.accountType() == eMyMoney::Account::AssetLoan) {
interest = -interest;
amortization = -amortization;
}
amortizationSplit.setShares(amortization);
if (interestSplitValid)
interestSplit.setShares(interest);
// FIXME: for now we only assume loans to be in the currency of the transaction
amortizationSplit.setValue(amortization);
if (interestSplitValid)
interestSplit.setValue(interest);
transaction.modifySplit(amortizationSplit);
if (interestSplitValid)
transaction.modifySplit(interestSplit);
}
}
}
diff --git a/kmymoney/mymoney/mymoneyreport.cpp b/kmymoney/mymoney/mymoneyreport.cpp
index e0cb02b79..9d79c8443 100644
--- a/kmymoney/mymoney/mymoneyreport.cpp
+++ b/kmymoney/mymoney/mymoneyreport.cpp
@@ -1,1038 +1,1039 @@
/***************************************************************************
mymoneyreport.cpp
-------------------
begin : Sun July 4 2004
copyright : (C) 2004-2005 by Ace Jones
email : acejones@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 "mymoneyreport.h"
#include "kmymoneyglobalsettings.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QString>
#include <QList>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
#include "mymoneystoragenames.h"
+#include "mymoneytransaction.h"
using namespace MyMoneyStorageNodes;
// define this to debug reports
// #define DEBUG_REPORTS
const QStringList MyMoneyReport::kRowTypeText = QString("none,assetliability,expenseincome,category,topcategory,account,tag,payee,month,week,topaccount,topaccount-account,equitytype,accounttype,institution,budget,budgetactual,schedule,accountinfo,accountloaninfo,accountreconcile,cashflow").split(',');
const QStringList MyMoneyReport::kColumnTypeText = QString("none,months,bimonths,quarters,4,5,6,weeks,8,9,10,11,years").split(',');
// if you add names here, don't forget to update the bitmap for EQueryColumns
// and shift the bit for eQCend one position to the left
const QStringList MyMoneyReport::kQueryColumnsText = QString("none,number,payee,category,tag,memo,account,reconcileflag,action,shares,price,performance,loan,balance,capitalgain").split(',');
const MyMoneyReport::EReportType MyMoneyReport::kTypeArray[] = { eNoReport, ePivotTable, ePivotTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, ePivotTable, ePivotTable, eInfoTable, eInfoTable, eInfoTable, eQueryTable, eQueryTable, eNoReport };
const QStringList MyMoneyReport::kDetailLevelText = QString("none,all,top,group,total,invalid").split(',');
const QStringList MyMoneyReport::kChartTypeText = QString("none,line,bar,pie,ring,stackedbar").split(',');
// This should live in mymoney/mymoneytransactionfilter.h
const QStringList kTypeText = QString("all,payments,deposits,transfers,none").split(',');
const QStringList kStateText = QString("all,notreconciled,cleared,reconciled,frozen,none").split(',');
const QStringList kDateLockText = QString("alldates,untiltoday,currentmonth,currentyear,monthtodate,yeartodate,yeartomonth,lastmonth,lastyear,last7days,last30days,last3months,last6months,last12months,next7days,next30days,next3months,next6months,next12months,userdefined,last3tonext3months,last11Months,currentQuarter,lastQuarter,nextQuarter,currentFiscalYear,lastFiscalYear,today,next18months").split(',');
const QStringList kDataLockText = QString("automatic,userdefined").split(',');
const QStringList kAccountTypeText = QString("unknown,checkings,savings,cash,creditcard,loan,certificatedep,investment,moneymarket,asset,liability,currency,income,expense,assetloan,stock,equity,invalid").split(',');
MyMoneyReport::MyMoneyReport() :
m_name("Unconfigured Pivot Table Report"),
m_detailLevel(eDetailNone),
m_investmentSum(eSumSold),
m_hideTransactions(false),
m_convertCurrency(true),
m_favorite(false),
m_tax(false),
m_investments(false),
m_loans(false),
m_reportType(kTypeArray[eExpenseIncome]),
m_rowType(eExpenseIncome),
m_columnType(eMonths),
m_columnsAreDays(false),
m_queryColumns(eQCnone),
m_dateLock(MyMoneyTransactionFilter::userDefined),
m_accountGroupFilter(false),
m_chartType(eChartLine),
m_chartDataLabels(true),
m_chartCHGridLines(true),
m_chartSVGridLines(true),
m_chartByDefault(false),
m_logYaxis(false),
m_dataRangeStart('0'),
m_dataRangeEnd('0'),
m_dataMajorTick('0'),
m_dataMinorTick('0'),
m_yLabelsPrecision(2),
m_dataLock(MyMoneyReport::automatic),
m_includeSchedules(false),
m_includeTransfers(false),
m_includeBudgetActuals(false),
m_includeUnusedAccounts(false),
m_showRowTotals(false),
m_showColumnTotals(true),
m_includeForecast(false),
m_includeMovingAverage(false),
m_movingAverageDays(0),
m_includePrice(false),
m_includeAveragePrice(false),
m_mixedTime(false),
m_currentDateColumn(0),
m_settlementPeriod(3),
m_showSTLTCapitalGains(false),
m_tseparator(QDate::currentDate().addYears(-1)),
m_skipZero(false)
{
m_chartLineWidth = m_lineWidth;
}
MyMoneyReport::MyMoneyReport(const QString& id, const MyMoneyReport& right) :
MyMoneyObject(id),
m_movingAverageDays(0),
m_currentDateColumn(0)
{
*this = right;
setId(id);
}
MyMoneyReport::MyMoneyReport(ERowType _rt, unsigned _ct, dateOptionE _dl, EDetailLevel _ss, const QString& _name, const QString& _comment) :
m_name(_name),
m_comment(_comment),
m_detailLevel(_ss),
m_investmentSum(_ct & eQCcapitalgain ? eSumSold : eSumPeriod),
m_hideTransactions(false),
m_convertCurrency(true),
m_favorite(false),
m_tax(false),
m_investments(false),
m_loans(false),
m_reportType(kTypeArray[_rt]),
m_rowType(_rt),
m_columnType(eMonths),
m_columnsAreDays(false),
m_queryColumns(eQCnone),
m_dateLock(_dl),
m_accountGroupFilter(false),
m_chartType(eChartLine),
m_chartDataLabels(true),
m_chartCHGridLines(true),
m_chartSVGridLines(true),
m_chartByDefault(false),
m_logYaxis(false),
m_dataRangeStart('0'),
m_dataRangeEnd('0'),
m_dataMajorTick('0'),
m_dataMinorTick('0'),
m_yLabelsPrecision(2),
m_dataLock(MyMoneyReport::automatic),
m_includeSchedules(false),
m_includeTransfers(false),
m_includeBudgetActuals(false),
m_includeUnusedAccounts(false),
m_showRowTotals(false),
m_showColumnTotals(true),
m_includeForecast(false),
m_includeMovingAverage(false),
m_movingAverageDays(0),
m_includePrice(false),
m_includeAveragePrice(false),
m_mixedTime(false),
m_currentDateColumn(0),
m_settlementPeriod(3),
m_showSTLTCapitalGains(false),
m_tseparator(QDate::currentDate().addYears(-1)),
m_skipZero(false)
{
//set initial values
m_chartLineWidth = m_lineWidth;
//set report type
if (m_reportType == ePivotTable)
m_columnType = static_cast<EColumnType>(_ct);
if (m_reportType == eQueryTable)
m_queryColumns = static_cast<EQueryColumns>(_ct);
setDateFilter(_dl);
//throw exception if the type is inconsistent
if ((_rt > static_cast<ERowType>(sizeof(kTypeArray) / sizeof(kTypeArray[0])))
|| (m_reportType == eNoReport))
throw MYMONEYEXCEPTION("Invalid report type");
//add the corresponding account groups
if (_rt == MyMoneyReport::eAssetLiability) {
- addAccountGroup(MyMoneyAccount::Asset);
- addAccountGroup(MyMoneyAccount::Liability);
+ addAccountGroup(eMyMoney::Account::Asset);
+ addAccountGroup(eMyMoney::Account::Liability);
m_showRowTotals = true;
}
if (_rt == MyMoneyReport::eAccount) {
- addAccountGroup(MyMoneyAccount::Asset);
- addAccountGroup(MyMoneyAccount::AssetLoan);
- addAccountGroup(MyMoneyAccount::Cash);
- addAccountGroup(MyMoneyAccount::Checkings);
- addAccountGroup(MyMoneyAccount::CreditCard);
+ addAccountGroup(eMyMoney::Account::Asset);
+ addAccountGroup(eMyMoney::Account::AssetLoan);
+ addAccountGroup(eMyMoney::Account::Cash);
+ addAccountGroup(eMyMoney::Account::Checkings);
+ addAccountGroup(eMyMoney::Account::CreditCard);
if (KMyMoneyGlobalSettings::expertMode())
- addAccountGroup(MyMoneyAccount::Equity);
- addAccountGroup(MyMoneyAccount::Expense);
- addAccountGroup(MyMoneyAccount::Income);
- addAccountGroup(MyMoneyAccount::Liability);
- addAccountGroup(MyMoneyAccount::Loan);
- addAccountGroup(MyMoneyAccount::Savings);
- addAccountGroup(MyMoneyAccount::Stock);
+ addAccountGroup(eMyMoney::Account::Equity);
+ addAccountGroup(eMyMoney::Account::Expense);
+ addAccountGroup(eMyMoney::Account::Income);
+ addAccountGroup(eMyMoney::Account::Liability);
+ addAccountGroup(eMyMoney::Account::Loan);
+ addAccountGroup(eMyMoney::Account::Savings);
+ addAccountGroup(eMyMoney::Account::Stock);
m_showRowTotals = true;
}
if (_rt == MyMoneyReport::eExpenseIncome) {
- addAccountGroup(MyMoneyAccount::Expense);
- addAccountGroup(MyMoneyAccount::Income);
+ addAccountGroup(eMyMoney::Account::Expense);
+ addAccountGroup(eMyMoney::Account::Income);
m_showRowTotals = true;
}
//FIXME take this out once we have sorted out all issues regarding budget of assets and liabilities -- asoliverez@gmail.com
if (_rt == MyMoneyReport::eBudget || _rt == MyMoneyReport::eBudgetActual) {
- addAccountGroup(MyMoneyAccount::Expense);
- addAccountGroup(MyMoneyAccount::Income);
+ addAccountGroup(eMyMoney::Account::Expense);
+ addAccountGroup(eMyMoney::Account::Income);
}
if (_rt == MyMoneyReport::eAccountInfo) {
- addAccountGroup(MyMoneyAccount::Asset);
- addAccountGroup(MyMoneyAccount::Liability);
+ addAccountGroup(eMyMoney::Account::Asset);
+ addAccountGroup(eMyMoney::Account::Liability);
}
//cash flow reports show splits for all account groups
if (_rt == MyMoneyReport::eCashFlow) {
- addAccountGroup(MyMoneyAccount::Expense);
- addAccountGroup(MyMoneyAccount::Income);
- addAccountGroup(MyMoneyAccount::Asset);
- addAccountGroup(MyMoneyAccount::Liability);
+ addAccountGroup(eMyMoney::Account::Expense);
+ addAccountGroup(eMyMoney::Account::Income);
+ addAccountGroup(eMyMoney::Account::Asset);
+ addAccountGroup(eMyMoney::Account::Liability);
}
#ifdef DEBUG_REPORTS
QDebug out = qDebug();
out << _name << toString(_rt) << toString(m_reportType);
- foreach(const MyMoneyAccount::accountTypeE accountType, m_accountGroups)
+ foreach(const eMyMoney::Account accountType, m_accountGroups)
out << MyMoneyAccount::accountTypeToString(accountType);
if (m_accounts.size() > 0)
out << m_accounts;
#endif
}
MyMoneyReport::MyMoneyReport(const QDomElement& node) :
MyMoneyObject(node),
m_currentDateColumn(0)
{
// properly initialize the object before reading it
*this = MyMoneyReport();
if (!read(node))
clearId();
}
void MyMoneyReport::clearTransactionFilter()
{
m_accountGroupFilter = false;
m_accountGroups.clear();
MyMoneyTransactionFilter::clear();
}
void MyMoneyReport::validDateRange(QDate& _db, QDate& _de)
{
_db = fromDate();
_de = toDate();
// if either begin or end date are invalid we have one of the following
// possible date filters:
//
// a) begin date not set - first transaction until given end date
// b) end date not set - from given date until last transaction
// c) both not set - first transaction until last transaction
//
// If there is no transaction in the engine at all, we use the current
// year as the filter criteria.
if (!_db.isValid() || !_de.isValid()) {
QList<MyMoneyTransaction> list = MyMoneyFile::instance()->transactionList(*this);
QDate tmpBegin, tmpEnd;
if (!list.isEmpty()) {
qSort(list);
// try to use the post dates
tmpBegin = list.front().postDate();
tmpEnd = list.back().postDate();
// if the post dates are not valid try the entry dates
if (!tmpBegin.isValid())
tmpBegin = list.front().entryDate();
if (!tmpEnd.isValid())
tmpEnd = list.back().entryDate();
}
// make sure that we leave this function with valid dates no mather what
if (!tmpBegin.isValid() || !tmpEnd.isValid() || tmpBegin > tmpEnd) {
tmpBegin = QDate(QDate::currentDate().year(), 1, 1); // the first date in the file
tmpEnd = QDate(QDate::currentDate().year(), 12, 31); // the last date in the file
}
if (!_db.isValid())
_db = tmpBegin;
if (!_de.isValid())
_de = tmpEnd;
}
if (_db > _de)
_db = _de;
}
void MyMoneyReport::setRowType(ERowType _rt)
{
m_rowType = _rt;
m_reportType = kTypeArray[_rt];
m_accountGroupFilter = false;
m_accountGroups.clear();
if (_rt == MyMoneyReport::eAssetLiability) {
- addAccountGroup(MyMoneyAccount::Asset);
- addAccountGroup(MyMoneyAccount::Liability);
+ addAccountGroup(eMyMoney::Account::Asset);
+ addAccountGroup(eMyMoney::Account::Liability);
}
if (_rt == MyMoneyReport::eExpenseIncome) {
- addAccountGroup(MyMoneyAccount::Expense);
- addAccountGroup(MyMoneyAccount::Income);
+ addAccountGroup(eMyMoney::Account::Expense);
+ addAccountGroup(eMyMoney::Account::Income);
}
}
-bool MyMoneyReport::accountGroups(QList<MyMoneyAccount::accountTypeE>& list) const
+bool MyMoneyReport::accountGroups(QList<eMyMoney::Account>& list) const
{
bool result = m_accountGroupFilter;
if (result) {
- QList<MyMoneyAccount::accountTypeE>::const_iterator it_group = m_accountGroups.begin();
+ QList<eMyMoney::Account>::const_iterator it_group = m_accountGroups.begin();
while (it_group != m_accountGroups.end()) {
list += (*it_group);
++it_group;
}
}
return result;
}
-void MyMoneyReport::addAccountGroup(MyMoneyAccount::accountTypeE type)
+void MyMoneyReport::addAccountGroup(eMyMoney::Account type)
{
- if (!m_accountGroups.isEmpty() && type != MyMoneyAccount::UnknownAccountType) {
+ if (!m_accountGroups.isEmpty() && type != eMyMoney::Account::Unknown) {
if (m_accountGroups.contains(type))
return;
}
m_accountGroupFilter = true;
- if (type != MyMoneyAccount::UnknownAccountType)
+ if (type != eMyMoney::Account::Unknown)
m_accountGroups.push_back(type);
}
-bool MyMoneyReport::includesAccountGroup(MyMoneyAccount::accountTypeE type) const
+bool MyMoneyReport::includesAccountGroup(eMyMoney::Account type) const
{
bool result = (! m_accountGroupFilter)
|| (isIncludingTransfers() && m_rowType == MyMoneyReport::eExpenseIncome)
|| m_accountGroups.contains(type);
return result;
}
bool MyMoneyReport::includes(const MyMoneyAccount& acc) const
{
bool result = false;
if (includesAccountGroup(acc.accountGroup())) {
switch (acc.accountGroup()) {
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
+ case eMyMoney::Account::Income:
+ case eMyMoney::Account::Expense:
if (isTax())
result = (acc.value("Tax") == "Yes") && includesCategory(acc.id());
else
result = includesCategory(acc.id());
break;
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::Liability:
+ case eMyMoney::Account::Asset:
+ case eMyMoney::Account::Liability:
if (isLoansOnly())
result = acc.isLoan() && includesAccount(acc.id());
else if (isInvestmentsOnly())
result = acc.isInvest() && includesAccount(acc.id());
else if (isIncludingTransfers() && m_rowType == MyMoneyReport::eExpenseIncome)
// If transfers are included, ONLY include this account if it is NOT
// included in the report itself!!
result = ! includesAccount(acc.id());
else
result = includesAccount(acc.id());
break;
- case MyMoneyAccount::Equity:
+ case eMyMoney::Account::Equity:
if (isInvestmentsOnly())
result = (isIncludingPrice() || isIncludingAveragePrice()) && acc.isInvest() && includesAccount(acc.id());
break;
default:
result = includesAccount(acc.id());
}
}
return result;
}
void MyMoneyReport::write(QDomElement& e, QDomDocument *doc, bool anonymous) const
{
// No matter what changes, be sure to have a 'type' attribute. Only change
// the major type if it becomes impossible to maintain compatibility with
// older versions of the program as new features are added to the reports.
// Feel free to change the minor type every time a change is made here.
// write report's internals
if (m_reportType == ePivotTable)
e.setAttribute(getAttrName(anType), "pivottable 1.15");
else if (m_reportType == eQueryTable)
e.setAttribute(getAttrName(anType), "querytable 1.14");
else if (m_reportType == eInfoTable)
e.setAttribute(getAttrName(anType), "infotable 1.0");
e.setAttribute(getAttrName(anGroup), m_group);
e.setAttribute(getAttrName(anID), m_id);
// write general tab
if (anonymous) {
e.setAttribute(getAttrName(anName), m_id);
e.setAttribute(getAttrName(anComment), QString(m_comment).fill('x'));
} else {
e.setAttribute(getAttrName(anName), m_name);
e.setAttribute(getAttrName(anComment), m_comment);
}
e.setAttribute(getAttrName(anConvertCurrency), m_convertCurrency);
e.setAttribute(getAttrName(anFavorite), m_favorite);
e.setAttribute(getAttrName(anSkipZero), m_skipZero);
e.setAttribute(getAttrName(anDateLock), kDateLockText[m_dateLock]);
if (m_reportType == ePivotTable) {
// write report's internals
e.setAttribute(getAttrName(anIncludesActuals), m_includeBudgetActuals);
e.setAttribute(getAttrName(anIncludesForecast), m_includeForecast);
e.setAttribute(getAttrName(anIncludesPrice), m_includePrice);
e.setAttribute(getAttrName(anIncludesAveragePrice), m_includeAveragePrice);
e.setAttribute(getAttrName(anMixedTime), m_mixedTime);
e.setAttribute(getAttrName(anInvestments), m_investments); // it's setable in rows/columns tab of querytable, but here it is internal setting
// write rows/columns tab
if (!m_budgetId.isEmpty())
e.setAttribute(getAttrName(anBudget), m_budgetId);
e.setAttribute(getAttrName(anRowType), kRowTypeText[m_rowType]);
e.setAttribute(getAttrName(anShowRowTotals), m_showRowTotals);
e.setAttribute(getAttrName(anShowColumnTotals), m_showColumnTotals);
e.setAttribute(getAttrName(anDetail), kDetailLevelText[m_detailLevel]);
e.setAttribute(getAttrName(anIncludesMovingAverage), m_includeMovingAverage);
if (m_includeMovingAverage)
e.setAttribute(getAttrName(anMovingAverageDays), m_movingAverageDays);
e.setAttribute(getAttrName(anIncludesSchedules), m_includeSchedules);
e.setAttribute(getAttrName(anIncludesTransfers), m_includeTransfers);
e.setAttribute(getAttrName(anIncludesUnused), m_includeUnusedAccounts);
e.setAttribute(getAttrName(anColumnsAreDays), m_columnsAreDays);
// write chart tab
if (m_chartType < 0 || m_chartType >= kChartTypeText.size()) {
qDebug("m_chartType out of bounds with %d on report of type %d. Default to none.", m_chartType, m_reportType);
e.setAttribute(getAttrName(anChartType), kChartTypeText[eChartNone]);
} else
e.setAttribute(getAttrName(anChartType), kChartTypeText[m_chartType]);
e.setAttribute(getAttrName(anChartCHGridLines), m_chartCHGridLines);
e.setAttribute(getAttrName(anChartSVGridLines), m_chartSVGridLines);
e.setAttribute(getAttrName(anChartDataLabels), m_chartDataLabels);
e.setAttribute(getAttrName(anChartByDefault), m_chartByDefault);
e.setAttribute(getAttrName(anLogYAxis), m_logYaxis);
e.setAttribute(getAttrName(anChartLineWidth), m_chartLineWidth);
e.setAttribute(getAttrName(anColumnType), kColumnTypeText[m_columnType]);
e.setAttribute(getAttrName(anDataLock), kDataLockText[m_dataLock]);
e.setAttribute(getAttrName(anDataRangeStart), m_dataRangeStart);
e.setAttribute(getAttrName(anDataRangeEnd), m_dataRangeEnd);
e.setAttribute(getAttrName(anDataMajorTick), m_dataMajorTick);
e.setAttribute(getAttrName(anDataMinorTick), m_dataMinorTick);
e.setAttribute(getAttrName(anYLabelsPrecision), m_yLabelsPrecision);
} else if (m_reportType == eQueryTable) {
// write rows/columns tab
e.setAttribute(getAttrName(anRowType), kRowTypeText[m_rowType]);
QStringList columns;
unsigned qc = m_queryColumns;
unsigned it_qc = eQCbegin;
unsigned index = 1;
while (it_qc != eQCend) {
if (qc & it_qc)
columns += kQueryColumnsText[index];
it_qc *= 2;
index++;
}
e.setAttribute(getAttrName(anQueryColumns), columns.join(","));
e.setAttribute(getAttrName(anTax), m_tax);
e.setAttribute(getAttrName(anInvestments), m_investments);
e.setAttribute(getAttrName(anLoans), m_loans);
e.setAttribute(getAttrName(anHideTransactions), m_hideTransactions);
e.setAttribute(getAttrName(anShowColumnTotals), m_showColumnTotals);
e.setAttribute(getAttrName(anDetail), kDetailLevelText[m_detailLevel]);
// write performance tab
if (m_queryColumns & eQCperformance || m_queryColumns & eQCcapitalgain)
e.setAttribute(getAttrName(anInvestmentSum), m_investmentSum);
// write capital gains tab
if (m_queryColumns & eQCcapitalgain) {
if (m_investmentSum == MyMoneyReport::eSumSold) {
e.setAttribute(getAttrName(anSettlementPeriod), m_settlementPeriod);
e.setAttribute(getAttrName(anShowSTLTCapitalGains), m_showSTLTCapitalGains);
e.setAttribute(getAttrName(anTermsSeparator), m_tseparator.toString(Qt::ISODate));
}
}
} else if (m_reportType == eInfoTable)
e.setAttribute(getAttrName(anShowRowTotals), m_showRowTotals);
//
// Text Filter
//
QRegExp textfilter;
if (textFilter(textfilter)) {
QDomElement f = doc->createElement(getElName(enText));
f.setAttribute(getAttrName(anPattern), textfilter.pattern());
f.setAttribute(getAttrName(anCaseSensitive), (textfilter.caseSensitivity() == Qt::CaseSensitive) ? 1 : 0);
f.setAttribute(getAttrName(anRegEx), (textfilter.patternSyntax() == QRegExp::Wildcard) ? 1 : 0);
f.setAttribute(getAttrName(anInvertText), m_invertText);
e.appendChild(f);
}
//
// Type & State Filters
//
QList<int> typelist;
if (types(typelist) && ! typelist.empty()) {
// iterate over payees, and add each one
QList<int>::const_iterator it_type = typelist.constBegin();
while (it_type != typelist.constEnd()) {
QDomElement p = doc->createElement(getElName(enType));
p.setAttribute(getAttrName(anType), kTypeText[*it_type]);
e.appendChild(p);
++it_type;
}
}
QList<int> statelist;
if (states(statelist) && ! statelist.empty()) {
// iterate over payees, and add each one
QList<int>::const_iterator it_state = statelist.constBegin();
while (it_state != statelist.constEnd()) {
QDomElement p = doc->createElement(getElName(enState));
p.setAttribute(getAttrName(anState), kStateText[*it_state]);
e.appendChild(p);
++it_state;
}
}
//
// Number Filter
//
QString nrFrom, nrTo;
if (numberFilter(nrFrom, nrTo)) {
QDomElement f = doc->createElement(getElName(enNumber));
f.setAttribute(getAttrName(anFrom), nrFrom);
f.setAttribute(getAttrName(anTo), nrTo);
e.appendChild(f);
}
//
// Amount Filter
//
MyMoneyMoney from, to;
if (amountFilter(from, to)) { // bool getAmountFilter(MyMoneyMoney&,MyMoneyMoney&);
QDomElement f = doc->createElement(getElName(enAmount));
f.setAttribute(getAttrName(anFrom), from.toString());
f.setAttribute(getAttrName(anTo), to.toString());
e.appendChild(f);
}
//
// Payees Filter
//
QStringList payeelist;
if (payees(payeelist)) {
if (payeelist.empty()) {
QDomElement p = doc->createElement(getElName(enPayee));
e.appendChild(p);
} else {
// iterate over payees, and add each one
QStringList::const_iterator it_payee = payeelist.constBegin();
while (it_payee != payeelist.constEnd()) {
QDomElement p = doc->createElement(getElName(enPayee));
p.setAttribute(getAttrName(anID), *it_payee);
e.appendChild(p);
++it_payee;
}
}
}
//
// Tags Filter
//
QStringList taglist;
if (tags(taglist)) {
if (taglist.empty()) {
QDomElement p = doc->createElement(getElName(enTag));
e.appendChild(p);
} else {
// iterate over tags, and add each one
QStringList::const_iterator it_tag = taglist.constBegin();
while (it_tag != taglist.constEnd()) {
QDomElement p = doc->createElement(getElName(enTag));
p.setAttribute(getAttrName(anID), *it_tag);
e.appendChild(p);
++it_tag;
}
}
}
//
// Account Groups Filter
//
- QList<MyMoneyAccount::accountTypeE> accountgrouplist;
+ QList<eMyMoney::Account> accountgrouplist;
if (accountGroups(accountgrouplist)) {
// iterate over accounts, and add each one
- QList<MyMoneyAccount::accountTypeE>::const_iterator it_group = accountgrouplist.constBegin();
+ QList<eMyMoney::Account>::const_iterator it_group = accountgrouplist.constBegin();
while (it_group != accountgrouplist.constEnd()) {
QDomElement p = doc->createElement(getElName(enAccountGroup));
- p.setAttribute(getAttrName(anGroup), kAccountTypeText[*it_group]);
+ p.setAttribute(getAttrName(anGroup), kAccountTypeText[(int)*it_group]);
e.appendChild(p);
++it_group;
}
}
//
// Accounts Filter
//
QStringList accountlist;
if (accounts(accountlist)) {
// iterate over accounts, and add each one
QStringList::const_iterator it_account = accountlist.constBegin();
while (it_account != accountlist.constEnd()) {
QDomElement p = doc->createElement(getElName(enAccount));
p.setAttribute(getAttrName(anID), *it_account);
e.appendChild(p);
++it_account;
}
}
//
// Categories Filter
//
accountlist.clear();
if (categories(accountlist)) {
// iterate over accounts, and add each one
QStringList::const_iterator it_account = accountlist.constBegin();
while (it_account != accountlist.constEnd()) {
QDomElement p = doc->createElement(getElName(enCategory));
p.setAttribute(getAttrName(anID), *it_account);
e.appendChild(p);
++it_account;
}
}
//
// Date Filter
//
if (m_dateLock == MyMoneyTransactionFilter::userDefined) {
QDate dateFrom, dateTo;
if (dateFilter(dateFrom, dateTo)) {
QDomElement f = doc->createElement(getElName(enDates));
if (dateFrom.isValid())
f.setAttribute(getAttrName(anFrom), dateFrom.toString(Qt::ISODate));
if (dateTo.isValid())
f.setAttribute(getAttrName(anTo), dateTo.toString(Qt::ISODate));
e.appendChild(f);
}
}
}
bool MyMoneyReport::read(const QDomElement& e)
{
// The goal of this reading method is 100% backward AND 100% forward
// compatibility. Any report ever created with any version of KMyMoney
// should be able to be loaded by this method (as long as it's one of the
// report types supported in this version, of course)
if (e.tagName().compare(nodeNames[nnReport]) != 0)
return false;
// read report's internals
QString type = e.attribute(getAttrName(anType));
if (type.startsWith(QLatin1String("pivottable")))
m_reportType = ePivotTable;
else if (type.startsWith(QLatin1String("querytable")))
m_reportType = eQueryTable;
else if (type.startsWith(QLatin1String("infotable")))
m_reportType = eInfoTable;
else
return false;
m_group = e.attribute(getAttrName(anGroup));
m_id = e.attribute(getAttrName(anID));
clearTransactionFilter();
// read date tab
QString datelockstr = e.attribute(getAttrName(anDateLock), "userdefined");
// Handle the pivot 1.2/query 1.1 case where the values were saved as
// numbers
bool ok = false;
int i = datelockstr.toUInt(&ok);
if (!ok) {
i = kDateLockText.indexOf(datelockstr);
if (i == -1)
i = MyMoneyTransactionFilter::userDefined;
}
setDateFilter(static_cast<dateOptionE>(i));
// read general tab
m_name = e.attribute(getAttrName(anName));
m_comment = e.attribute(getAttrName(anComment), "Extremely old report");
m_convertCurrency = e.attribute(getAttrName(anConvertCurrency), "1").toUInt();
m_favorite = e.attribute(getAttrName(anFavorite), "0").toUInt();
m_skipZero = e.attribute(getAttrName(anSkipZero), "0").toUInt();
if (m_reportType == ePivotTable) {
// read report's internals
m_includeBudgetActuals = e.attribute(getAttrName(anIncludesActuals), "0").toUInt();
m_includeForecast = e.attribute(getAttrName(anIncludesForecast), "0").toUInt();
m_includePrice = e.attribute(getAttrName(anIncludesPrice), "0").toUInt();
m_includeAveragePrice = e.attribute(getAttrName(anIncludesAveragePrice), "0").toUInt();
m_mixedTime = e.attribute(getAttrName(anMixedTime), "0").toUInt();
m_investments = e.attribute(getAttrName(anInvestments), "0").toUInt();
// read rows/columns tab
if (e.hasAttribute(getAttrName(anBudget)))
m_budgetId = e.attribute(getAttrName(anBudget));
i = kRowTypeText.indexOf(e.attribute(getAttrName(anRowType)));
if (i != -1)
setRowType(static_cast<ERowType>(i));
else
setRowType(eExpenseIncome);
if (e.hasAttribute(getAttrName(anShowRowTotals)))
m_showRowTotals = e.attribute(getAttrName(anShowRowTotals)).toUInt();
else if (rowType() == eExpenseIncome) // for backward compatibility
m_showRowTotals = true;
m_showColumnTotals = e.attribute(getAttrName(anShowColumnTotals), "1").toUInt();
//check for reports with older settings which didn't have the detail attribute
i = kDetailLevelText.indexOf(e.attribute(getAttrName(anDetail)));
if (i != -1)
m_detailLevel = static_cast<EDetailLevel>(i);
else
m_detailLevel = eDetailAll;
m_includeMovingAverage = e.attribute(getAttrName(anIncludesMovingAverage), "0").toUInt();
if (m_includeMovingAverage)
m_movingAverageDays = e.attribute(getAttrName(anMovingAverageDays), "1").toUInt();
m_includeSchedules = e.attribute(getAttrName(anIncludesSchedules), "0").toUInt();
m_includeTransfers = e.attribute(getAttrName(anIncludesTransfers), "0").toUInt();
m_includeUnusedAccounts = e.attribute(getAttrName(anIncludesUnused), "0").toUInt();
m_columnsAreDays = e.attribute(getAttrName(anColumnsAreDays), "0").toUInt();
// read chart tab
i = kChartTypeText.indexOf(e.attribute(getAttrName(anChartType)));
if (i != -1)
m_chartType = static_cast<EChartType>(i);
else
m_chartType = eChartNone;
m_chartCHGridLines = e.attribute(getAttrName(anChartCHGridLines), "1").toUInt();
m_chartSVGridLines = e.attribute(getAttrName(anChartSVGridLines), "1").toUInt();
m_chartDataLabels = e.attribute(getAttrName(anChartDataLabels), "1").toUInt();
m_chartByDefault = e.attribute(getAttrName(anChartByDefault), "0").toUInt();
m_logYaxis = e.attribute(getAttrName(anLogYAxis), "0").toUInt();
m_chartLineWidth = e.attribute(getAttrName(anChartLineWidth), QString(m_lineWidth)).toUInt();
// read range tab
i = kColumnTypeText.indexOf(e.attribute(getAttrName(anColumnType)));
if (i != -1)
setColumnType(static_cast<EColumnType>(i));
else
setColumnType(eMonths);
i = kDataLockText.indexOf(e.attribute(getAttrName(anDataLock)));
if (i != -1)
setDataFilter(static_cast<dataOptionE>(i));
else
setDataFilter(MyMoneyReport::automatic);
m_dataRangeStart = e.attribute(getAttrName(anDataRangeStart), "0");
m_dataRangeEnd = e.attribute(getAttrName(anDataRangeEnd), "0");
m_dataMajorTick = e.attribute(getAttrName(anDataMajorTick), "0");
m_dataMinorTick = e.attribute(getAttrName(anDataMinorTick), "0");
m_yLabelsPrecision = e.attribute(getAttrName(anYLabelsPrecision), "2").toUInt();
} else if (m_reportType == eQueryTable) {
// read rows/columns tab
i = kRowTypeText.indexOf(e.attribute(getAttrName(anRowType)));
if (i != -1)
setRowType(static_cast<ERowType>(i));
else
setRowType(eAccount);
unsigned qc = 0;
QStringList columns = e.attribute(getAttrName(anQueryColumns), "none").split(',');
foreach (const auto column, columns) {
i = kQueryColumnsText.indexOf(column);
if (i > 0)
qc |= (1 << (i - 1));
}
setQueryColumns(static_cast<EQueryColumns>(qc));
m_tax = e.attribute(getAttrName(anTax), "0").toUInt();
m_investments = e.attribute(getAttrName(anInvestments), "0").toUInt();
m_loans = e.attribute(getAttrName(anLoans), "0").toUInt();
m_hideTransactions = e.attribute(getAttrName(anHideTransactions), "0").toUInt();
m_showColumnTotals = e.attribute(getAttrName(anShowColumnTotals), "1").toUInt();
m_detailLevel = kDetailLevelText.indexOf(e.attribute(getAttrName(anDetail), "none")) == eDetailAll ? eDetailAll : eDetailNone;
// read performance or capital gains tab
if (m_queryColumns & eQCperformance)
m_investmentSum = static_cast<EInvestmentSum>(e.attribute(getAttrName(anInvestmentSum), QString().setNum(MyMoneyReport::eSumPeriod)).toInt());
// read capital gains tab
if (m_queryColumns & eQCcapitalgain) {
m_investmentSum = static_cast<EInvestmentSum>(e.attribute(getAttrName(anInvestmentSum), QString().setNum(MyMoneyReport::eSumSold)).toInt());
if (m_investmentSum == MyMoneyReport::eSumSold) {
m_showSTLTCapitalGains = e.attribute(getAttrName(anShowSTLTCapitalGains), "0").toUInt();
m_settlementPeriod = e.attribute(getAttrName(anSettlementPeriod), "3").toUInt();
m_tseparator = QDate::fromString(e.attribute(getAttrName(anTermsSeparator), QDate::currentDate().addYears(-1).toString(Qt::ISODate)),Qt::ISODate);
}
}
} else if (m_reportType == eInfoTable) {
if (e.hasAttribute(getAttrName(anShowRowTotals)))
m_showRowTotals = e.attribute(getAttrName(anShowRowTotals)).toUInt();
else
m_showRowTotals = true;
}
QDomNode child = e.firstChild();
while (!child.isNull() && child.isElement()) {
QDomElement c = child.toElement();
if (getElName(enText) == c.tagName() && c.hasAttribute(getAttrName(anPattern))) {
setTextFilter(QRegExp(c.attribute(getAttrName(anPattern)),
c.attribute(getAttrName(anCaseSensitive), "1").toUInt()
? Qt::CaseSensitive : Qt::CaseInsensitive,
c.attribute(getAttrName(anRegEx), "1").toUInt()
? QRegExp::Wildcard : QRegExp::RegExp),
c.attribute(getAttrName(anInvertText), "0").toUInt());
}
if (getElName(enType) == c.tagName() && c.hasAttribute(getAttrName(anType))) {
i = kTypeText.indexOf(c.attribute(getAttrName(anType)));
if (i != -1)
addType(i);
}
if (getElName(enState) == c.tagName() && c.hasAttribute(getAttrName(anState))) {
i = kStateText.indexOf(c.attribute(getAttrName(anState)));
if (i != -1)
addState(i);
}
if (getElName(enNumber) == c.tagName())
setNumberFilter(c.attribute(getAttrName(anFrom)), c.attribute(getAttrName(anTo)));
if (getElName(enAmount) == c.tagName())
setAmountFilter(MyMoneyMoney(c.attribute(getAttrName(anFrom), "0/100")), MyMoneyMoney(c.attribute(getAttrName(anTo), "0/100")));
if (getElName(enDates) == c.tagName()) {
QDate from, to;
if (c.hasAttribute(getAttrName(anFrom)))
from = QDate::fromString(c.attribute(getAttrName(anFrom)), Qt::ISODate);
if (c.hasAttribute(getAttrName(anTo)))
to = QDate::fromString(c.attribute(getAttrName(anTo)), Qt::ISODate);
MyMoneyTransactionFilter::setDateFilter(from, to);
}
if (getElName(enPayee) == c.tagName())
addPayee(c.attribute(getAttrName(anID)));
if (getElName(enTag) == c.tagName())
addTag(c.attribute(getAttrName(anID)));
if (getElName(enCategory) == c.tagName() && c.hasAttribute(getAttrName(anID)))
addCategory(c.attribute(getAttrName(anID)));
if (getElName(enAccount) == c.tagName() && c.hasAttribute(getAttrName(anID)))
addAccount(c.attribute(getAttrName(anID)));
if (getElName(enAccountGroup) == c.tagName() && c.hasAttribute(getAttrName(anGroup))) {
i = kAccountTypeText.indexOf(c.attribute(getAttrName(anGroup)));
if (i != -1)
- addAccountGroup(static_cast<MyMoneyAccount::accountTypeE>(i));
+ addAccountGroup(static_cast<eMyMoney::Account>(i));
}
child = child.nextSibling();
}
return true;
}
void MyMoneyReport::writeXML(QDomDocument& document, QDomElement& parent) const
{
QDomElement el = document.createElement(nodeNames[nnReport]);
write(el, &document, false);
parent.appendChild(el);
}
bool MyMoneyReport::hasReferenceTo(const QString& id) const
{
QStringList list;
// collect all ids
accounts(list);
categories(list);
payees(list);
tags(list);
return (list.contains(id) > 0);
}
int MyMoneyReport::m_lineWidth = 2;
void MyMoneyReport::setLineWidth(int width)
{
m_lineWidth = width;
}
const QString MyMoneyReport::getElName(const elNameE _el)
{
static const QHash<elNameE, QString> elNames = {
{enPayee, QStringLiteral("PAYEE")},
{enTag, QStringLiteral("TAG")},
{enAccount, QStringLiteral("ACCOUNT")},
{enText, QStringLiteral("TEXT")},
{enType, QStringLiteral("TYPE")},
{enState, QStringLiteral("STATE")},
{enNumber, QStringLiteral("NUMBER")},
{enAmount, QStringLiteral("AMOUNT")},
{enDates, QStringLiteral("DATES")},
{enCategory, QStringLiteral("CATEGORY")},
{enAccountGroup, QStringLiteral("ACCOUNTGROUP")}
};
return elNames[_el];
}
const QString MyMoneyReport::getAttrName(const attrNameE _attr)
{
static const QHash<attrNameE, QString> attrNames = {
{anID, QStringLiteral("id")},
{anGroup, QStringLiteral("group")},
{anType, QStringLiteral("type")},
{anName, QStringLiteral("name")},
{anComment, QStringLiteral("comment")},
{anConvertCurrency, QStringLiteral("convertcurrency")},
{anFavorite, QStringLiteral("favorite")},
{anSkipZero, QStringLiteral("skipZero")},
{anDateLock, QStringLiteral("datelock")},
{anDataLock, QStringLiteral("datalock")},
{anMovingAverageDays, QStringLiteral("movingaveragedays")},
{anIncludesActuals, QStringLiteral("includesactuals")},
{anIncludesForecast, QStringLiteral("includesforecast")},
{anIncludesPrice, QStringLiteral("includesprice")},
{anIncludesAveragePrice, QStringLiteral("includesaverageprice")},
{anIncludesMovingAverage, QStringLiteral("includesmovingaverage")},
{anIncludesSchedules, QStringLiteral("includeschedules")},
{anIncludesTransfers, QStringLiteral("includestransfers")},
{anIncludesUnused, QStringLiteral("includeunused")},
{anMixedTime, QStringLiteral("mixedtime")},
{anInvestments, QStringLiteral("investments")},
{anBudget, QStringLiteral("budget")},
{anShowRowTotals, QStringLiteral("showrowtotals")},
{anShowColumnTotals, QStringLiteral("showcolumntotals")},
{anDetail, QStringLiteral("detail")},
{anColumnsAreDays, QStringLiteral("columnsaredays")},
{anChartType, QStringLiteral("charttype")},
{anChartCHGridLines, QStringLiteral("chartchgridlines")},
{anChartSVGridLines, QStringLiteral("chartsvgridlines")},
{anChartDataLabels, QStringLiteral("chartdatalabels")},
{anChartByDefault, QStringLiteral("chartbydefault")},
{anLogYAxis, QStringLiteral("logYaxis")},
{anChartLineWidth, QStringLiteral("chartlinewidth")},
{anColumnType, QStringLiteral("columntype")},
{anRowType, QStringLiteral("rowtype")},
{anDataRangeStart, QStringLiteral("dataRangeStart")},
{anDataRangeEnd, QStringLiteral("dataRangeEnd")},
{anDataMajorTick, QStringLiteral("dataMajorTick")},
{anDataMinorTick, QStringLiteral("dataMinorTick")},
{anYLabelsPrecision, QStringLiteral("yLabelsPrecision")},
{anQueryColumns, QStringLiteral("querycolumns")},
{anTax, QStringLiteral("tax")},
{anLoans, QStringLiteral("loans")},
{anHideTransactions, QStringLiteral("hidetransactions")},
{anInvestmentSum, QStringLiteral("investmentsum")},
{anSettlementPeriod, QStringLiteral("settlementperiod")},
{anShowSTLTCapitalGains, QStringLiteral("showSTLTCapitalGains")},
{anTermsSeparator, QStringLiteral("tseparator")},
{anPattern, QStringLiteral("pattern")},
{anCaseSensitive, QStringLiteral("casesensitive")},
{anRegEx, QStringLiteral("regex")},
{anInvertText, QStringLiteral("inverttext")},
{anState, QStringLiteral("state")},
{anFrom, QStringLiteral("from")},
{anTo, QStringLiteral("to")}
};
return attrNames[_attr];
}
QString MyMoneyReport::toString(ERowType type)
{
switch(type) {
case eNoRows : return "eNoRows";
case eAssetLiability : return "eAssetLiability";
case eExpenseIncome : return "eExpenseIncome";
case eCategory : return "eCategory";
case eTopCategory : return "eTopCategory";
case eAccount : return "eAccount";
case eTag : return "eTag";
case ePayee : return "ePayee";
case eMonth : return "eMonth";
case eWeek : return "eWeek";
case eTopAccount : return "eTopAccount";
case eAccountByTopAccount: return "eAccountByTopAccount";
case eEquityType : return "eEquityType";
case eAccountType : return "eAccountType";
case eInstitution : return "eInstitution";
case eBudget : return "eBudget";
case eBudgetActual : return "eBudgetActual";
case eSchedule : return "eSchedule";
case eAccountInfo : return "eAccountInfo";
case eAccountLoanInfo : return "eAccountLoanInfo";
case eAccountReconcile : return "eAccountReconcile";
case eCashFlow : return "eCashFlow";
default : return "undefined";
}
}
QString MyMoneyReport::toString(MyMoneyReport::EReportType type)
{
switch(type) {
case eNoReport: return "eNoReport";
case ePivotTable: return "ePivotTable";
case eQueryTable: return "eQueryTable";
case eInfoTable: return "eInfoTable";
default: return "undefined";
}
}
diff --git a/kmymoney/mymoney/mymoneyreport.h b/kmymoney/mymoney/mymoneyreport.h
index 79138c3cc..a27c2ebb4 100644
--- a/kmymoney/mymoney/mymoneyreport.h
+++ b/kmymoney/mymoney/mymoneyreport.h
@@ -1,891 +1,891 @@
/***************************************************************************
mymoneyreport.h
-------------------
begin : Sun July 4 2004
copyright : (C) 2004-2005 by Ace Jones
email : acejones@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 MYMONEYREPORT_H
#define MYMONEYREPORT_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
#include <QString>
class QDomElement;
class QDomDocument;
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyobject.h"
#include "mymoneyaccount.h"
#include "mymoneytransactionfilter.h"
#include "kmm_mymoney_export.h"
#include "mymoneyunittestable.h"
/**
* This class defines a report within the MyMoneyEngine. The report class
* contains all the configuration parameters needed to run a report, plus
* XML serialization.
*
* A report is a transactionfilter, so any report can specify which
* transactions it's interested down to the most minute level of detail.
* It extends the transactionfilter by providing identification (name,
* comments, group type, etc) as well as layout information (what kind
* of layout should be used, how the rows & columns should be presented,
* currency converted, etc.)
*
* As noted above, this class only provides a report DEFINITION. The
* generation and presentation of the report itself are left to higher
* level classes.
*
* @author Ace Jones <acejones@users.sourceforge.net>
*/
class KMM_MYMONEY_EXPORT MyMoneyReport: public MyMoneyObject, public MyMoneyTransactionFilter
{
Q_GADGET
KMM_MYMONEY_UNIT_TESTABLE
public:
// When adding a new row type, be sure to add a corresponding entry in kTypeArray
enum ERowType { eNoRows = 0, eAssetLiability, eExpenseIncome, eCategory, eTopCategory, eAccount, eTag, ePayee, eMonth, eWeek, eTopAccount, eAccountByTopAccount, eEquityType, eAccountType, eInstitution, eBudget, eBudgetActual, eSchedule, eAccountInfo, eAccountLoanInfo, eAccountReconcile, eCashFlow};
enum EReportType { eNoReport = 0, ePivotTable, eQueryTable, eInfoTable };
enum EColumnType { eNoColumns = 0, eDays = 1, eMonths = 1, eBiMonths = 2, eQuarters = 3, eWeeks = 7, eYears = 12 };
// if you add bits to this bitmask, start with the value currently assigned to eQCend and update its value afterwards
// also don't forget to add column names to kQueryColumnsText in mymoneyreport.cpp
enum EQueryColumns { eQCnone = 0x0, eQCbegin = 0x1, eQCnumber = 0x1, eQCpayee = 0x2, eQCcategory = 0x4, eQCtag = 0x8, eQCmemo = 0x10, eQCaccount = 0x20, eQCreconciled = 0x40, eQCaction = 0x80, eQCshares = 0x100, eQCprice = 0x200, eQCperformance = 0x400, eQCloan = 0x800, eQCbalance = 0x1000, eQCcapitalgain = 0x2000, eQCend = 0x4000 };
enum EDetailLevel { eDetailNone = 0, eDetailAll, eDetailTop, eDetailGroup, eDetailTotal, eDetailEnd };
enum EInvestmentSum { eSumPeriod = 0, eSumOwnedAndSold, eSumOwned, eSumSold, eSumBought};
enum EChartType { eChartNone = 0, eChartLine, eChartBar, eChartPie, eChartRing, eChartStackedBar, eChartEnd };
enum dataOptionE { automatic = 0, userDefined, dataOptionCount };
enum elNameE { enPayee ,enTag, enAccount, enText,
enType, enState, enNumber,
enAmount, enDates, enCategory,
enAccountGroup
};
Q_ENUM(elNameE)
enum attrNameE { anID, anGroup, anType, anName, anComment, anConvertCurrency, anFavorite,
anSkipZero, anDateLock, anDataLock, anMovingAverageDays,
anIncludesActuals, anIncludesForecast, anIncludesPrice,
anIncludesAveragePrice, anIncludesMovingAverage,
anIncludesSchedules, anIncludesTransfers, anIncludesUnused,
anMixedTime, anInvestments, anBudget,
anShowRowTotals, anShowColumnTotals, anDetail,
anColumnsAreDays, anChartType,
anChartCHGridLines, anChartSVGridLines,
anChartDataLabels, anChartByDefault,
anLogYAxis, anChartLineWidth, anColumnType, anRowType,
anDataRangeStart, anDataRangeEnd,
anDataMajorTick, anDataMinorTick,
anYLabelsPrecision, anQueryColumns,
anTax, anLoans, anHideTransactions, anInvestmentSum,
anSettlementPeriod, anShowSTLTCapitalGains, anTermsSeparator,
anPattern, anCaseSensitive, anRegEx, anInvertText, anState,
anFrom, anTo
};
Q_ENUM(attrNameE)
static const QStringList kRowTypeText;
static const QStringList kColumnTypeText;
static const QStringList kQueryColumnsText;
static const QStringList kDetailLevelText;
static const QStringList kChartTypeText;
static const EReportType kTypeArray[];
public:
MyMoneyReport();
MyMoneyReport(ERowType _rt, unsigned _ct, dateOptionE _dl, EDetailLevel _ss, const QString& _name, const QString& _comment);
MyMoneyReport(const QString& id, const MyMoneyReport& right);
/**
* This constructor creates an object based on the data found in the
* QDomElement referenced by @p node. If problems arise, the @p id of
* the object is cleared (see MyMoneyObject::clearId()).
*/
MyMoneyReport(const QDomElement& node);
// Simple get operations
const QString& name() const {
return m_name;
}
bool isShowingRowTotals() const {
return (m_showRowTotals);
}
bool isShowingColumnTotals() const {
return m_showColumnTotals;
}
EReportType reportType() const {
return m_reportType;
}
ERowType rowType() const {
return m_rowType;
}
EColumnType columnType() const {
return m_columnType;
}
bool isRunningSum() const {
return (m_rowType == eAssetLiability);
}
bool isConvertCurrency() const {
return m_convertCurrency;
}
unsigned columnPitch() const {
return static_cast<unsigned>(m_columnType);
}
const QString& comment() const {
return m_comment;
}
EQueryColumns queryColumns() const {
return m_queryColumns;
}
const QString& group() const {
return m_group;
}
bool isFavorite() const {
return m_favorite;
}
bool isTax() const {
return m_tax;
}
bool isInvestmentsOnly() const {
return m_investments;
}
bool isLoansOnly() const {
return m_loans;
}
EDetailLevel detailLevel() const {
return m_detailLevel;
}
EInvestmentSum investmentSum() const {
return m_investmentSum;
}
bool isHideTransactions() const {
return m_hideTransactions;
}
EChartType chartType() const {
return m_chartType;
}
bool isChartDataLabels() const {
return m_chartDataLabels;
}
bool isChartCHGridLines() const {
return m_chartCHGridLines;
}
bool isChartSVGridLines() const {
return m_chartSVGridLines;
}
bool isChartByDefault() const {
return m_chartByDefault;
}
uint chartLineWidth() const {
return m_chartLineWidth;
}
bool isLogYAxis() const {
return m_logYaxis;
}
const QString& dataRangeStart() const {
return m_dataRangeStart;
}
const QString& dataRangeEnd() const {
return m_dataRangeEnd;
}
const QString& dataMajorTick() const {
return m_dataMajorTick;
}
const QString& dataMinorTick() const {
return m_dataMinorTick;
}
uint yLabelsPrecision() const {
return m_yLabelsPrecision;
}
bool isIncludingSchedules() const {
return m_includeSchedules;
}
bool isColumnsAreDays() const {
return m_columnsAreDays;
}
bool isIncludingTransfers() const {
return m_includeTransfers;
}
bool isIncludingUnusedAccounts() const {
return m_includeUnusedAccounts;
}
bool hasBudget() const {
return !m_budgetId.isEmpty();
}
const QString& budget() const {
return m_budgetId;
}
bool isIncludingBudgetActuals() const {
return m_includeBudgetActuals;
}
bool isIncludingForecast() const {
return m_includeForecast;
}
bool isIncludingMovingAverage() const {
return m_includeMovingAverage;
}
int movingAverageDays() const {
return m_movingAverageDays;
}
bool isIncludingPrice() const {
return m_includePrice;
}
bool isIncludingAveragePrice() const {
return m_includeAveragePrice;
}
bool isDateUserDefined() const {
return m_dateLock == MyMoneyTransactionFilter::userDefined;
}
dateOptionE dateRange() const {
return m_dateLock;
}
bool isDataUserDefined() const {
return m_dataLock == MyMoneyReport::userDefined;
}
bool isMixedTime() const {
return m_mixedTime;
}
int currentDateColumn() const {
return m_currentDateColumn;
}
uint settlementPeriod() const {
return m_settlementPeriod;
}
bool isShowingSTLTCapitalGains() const {
return m_showSTLTCapitalGains;
}
const QDate& termSeparator() const {
return m_tseparator;
}
/**
* @see #m_skipZero
*/
bool isSkippingZero() const {
return m_skipZero;
}
// Simple set operations
void setName(const QString& _s) {
m_name = _s;
}
void setConvertCurrency(bool _f) {
m_convertCurrency = _f;
}
void setRowType(ERowType _rt);
void setColumnType(EColumnType _ct) {
m_columnType = _ct;
}
void setComment(const QString& _comment) {
m_comment = _comment;
}
void setGroup(const QString& _group) {
m_group = _group;
}
void setFavorite(bool _f) {
m_favorite = _f;
}
void setQueryColumns(EQueryColumns _qc) {
m_queryColumns = _qc;
}
void setTax(bool _f) {
m_tax = _f;
}
void setInvestmentsOnly(bool _f) {
m_investments = _f; if (_f) m_loans = false;
}
void setLoansOnly(bool _f) {
m_loans = _f; if (_f) m_investments = false;
}
void setDetailLevel(EDetailLevel _detail) {
m_detailLevel = _detail;
}
void setInvestmentSum(EInvestmentSum _sum) {
m_investmentSum = _sum;
}
void setHideTransactions(bool _f) {
m_hideTransactions = _f;
}
void setChartType(EChartType _type) {
m_chartType = _type;
}
void setChartDataLabels(bool _f) {
m_chartDataLabels = _f;
}
void setChartCHGridLines(bool _f) {
m_chartCHGridLines = _f;
}
void setChartSVGridLines(bool _f) {
m_chartSVGridLines = _f;
}
void setChartByDefault(bool _f) {
m_chartByDefault = _f;
}
void setChartLineWidth(uint _f) {
m_chartLineWidth = _f;
}
void setLogYAxis(bool _f) {
m_logYaxis = _f;
}
void setDataRangeStart(const QString& _f) {
m_dataRangeStart = _f;
}
void setDataRangeEnd(const QString& _f) {
m_dataRangeEnd = _f;
}
void setDataMajorTick(const QString& _f) {
m_dataMajorTick = _f;
}
void setDataMinorTick(const QString& _f) {
m_dataMinorTick = _f;
}
void setYLabelsPrecision(int _f) {
m_yLabelsPrecision = _f;
}
void setIncludingSchedules(bool _f) {
m_includeSchedules = _f;
}
void setColumnsAreDays(bool _f) {
m_columnsAreDays = _f;
}
void setIncludingTransfers(bool _f) {
m_includeTransfers = _f;
}
void setIncludingUnusedAccounts(bool _f) {
m_includeUnusedAccounts = _f;
}
void setShowingRowTotals(bool _f) {
m_showRowTotals = _f;
}
void setShowingColumnTotals(bool _f) {
m_showColumnTotals = _f;
}
void setIncludingBudgetActuals(bool _f) {
m_includeBudgetActuals = _f;
}
void setIncludingForecast(bool _f) {
m_includeForecast = _f;
}
void setIncludingMovingAverage(bool _f) {
m_includeMovingAverage = _f;
}
void setMovingAverageDays(int _days) {
m_movingAverageDays = _days;
}
void setIncludingPrice(bool _f) {
m_includePrice = _f;
}
void setIncludingAveragePrice(bool _f) {
m_includeAveragePrice = _f;
}
void setMixedTime(bool _f) {
m_mixedTime = _f;
}
void setCurrentDateColumn(int _f) {
m_currentDateColumn = _f;
}
void setSettlementPeriod(uint _days) {
m_settlementPeriod = _days;
}
void setShowSTLTCapitalGains(bool _f) {
m_showSTLTCapitalGains = _f;
}
void setTermSeparator(const QDate& _date) {
m_tseparator = _date;
}
/**
* @see #m_skipZero
*/
void setSkipZero(int _f) {
m_skipZero = _f;
}
/**
* Sets the budget used for this report
*
* @param _budget The ID of the budget to use, or an empty string
* to indicate a budget is NOT included
* @param _fa Whether to display actual data alongside the budget.
* Setting to false means the report displays ONLY the budget itself.
* @warning For now, the budget ID is ignored. The budget id is
* simply checked for any non-empty string, and if so, hasBudget()
* will return true.
*/
void setBudget(const QString& _budget, bool _fa = true) {
m_budgetId = _budget; m_includeBudgetActuals = _fa;
}
/**
* This method allows you to clear the underlying transaction filter
*/
void clearTransactionFilter();
/**
* This method allows you to set the underlying transaction filter
*
* @param _filter The filter which should replace the existing transaction
* filter.
*/
void assignFilter(const MyMoneyTransactionFilter& _filter) {
MyMoneyTransactionFilter::operator=(_filter);
}
/**
* Set the underlying date filter and LOCK that filter to the specified
* range. For example, if @p _u is "CurrentMonth", this report should always
* be updated to the current month no matter when the report is run.
*
* This updating is not entirely automatic, you should update it yourself by
* calling updateDateFilter.
*
* @param _u The date range constant (MyMoneyTransactionFilter::dateRangeE)
* which this report should be locked to.
*/
void setDateFilter(dateOptionE _u) {
m_dateLock = _u;
if (_u != MyMoneyTransactionFilter::userDefined)
MyMoneyTransactionFilter::setDateFilter(_u);
}
void setDataFilter(dataOptionE _u) {
m_dataLock = _u;
}
/**
* Set the underlying date filter using the start and end dates provided.
* Note that this does not LOCK to any range like setDateFilter(unsigned)
* above. It is just a reimplementation of the MyMoneyTransactionFilter
* version.
*
* @param _db The inclusive begin date of the date range
* @param _de The inclusive end date of the date range
*/
void setDateFilter(const QDate& _db, const QDate& _de) {
MyMoneyTransactionFilter::setDateFilter(_db, _de);
}
/**
* Set the underlying date filter using the 'date lock' property.
*
* Always call this function before executing the report to be sure that
* the date filters properly match the plain-language 'date lock'.
*
* For example, if the report is date-locked to "Current Month", and the
* last time you loaded or ran the report was in August, but it's now
* September, this function will update the date range to be September,
* as is proper.
*/
void updateDateFilter() {
if (m_dateLock != MyMoneyTransactionFilter::userDefined) MyMoneyTransactionFilter::setDateFilter(m_dateLock);
}
MyMoneyReport::dataOptionE dataFilter() {
return m_dataLock;
}
/**
* Retrieves a VALID beginning & ending date for this report.
*
* The underlying date filter can return en empty QDate() for either the
* begin or end date or both. This is typically unacceptable for reports,
* which need the REAL begin and end date.
*
* This function gets the underlying date filter range, and if either is
* an empty QDate(), it determines the missing date from looking at all
* the transactions which match the underlying filter, and returning the
* date of the first or last transaction (as appropriate).
*
* @param _db The inclusive begin date of the date range
* @param _de The inclusive end date of the date range
*/
void validDateRange(QDate& _db, QDate& _de);
/**
* This method turns on the account group filter and adds the
* @p type to the list of allowed groups.
*
* Note that account group filtering is handled differently
* than all the filters of the underlying class. This filter
* is meant to be applied to individual splits of matched
* transactions AFTER the underlying filter is used to find
* the matching transactions.
*
* @param type the account group to add to the allowed groups list
*/
- void addAccountGroup(MyMoneyAccount::accountTypeE type);
+ void addAccountGroup(eMyMoney::Account type);
/**
* This method returns whether an account group filter has been set,
* and if so, it returns all the account groups set in the filter.
*
* @param list list to append account groups into
* @return return true if an account group filter has been set
*/
- bool accountGroups(QList<MyMoneyAccount::accountTypeE>& list) const;
+ bool accountGroups(QList<eMyMoney::Account>& list) const;
/**
* This method returns whether the specified account group
* is allowed by the account groups filter.
*
* @param type group to append account groups into
* @return return true if an account group filter has been set
*/
- bool includesAccountGroup(MyMoneyAccount::accountTypeE type) const;
+ bool includesAccountGroup(eMyMoney::Account type) const;
/**
* This method is used to test whether a specific account
* passes the accountGroup test and either the Account or
* Category test, depending on which sort of Account it is.
*
* The m_tax and m_investments properties are also considered.
*
* @param acc the account in question
* @return true if account is in filter set, false otherwise
*/
bool includes(const MyMoneyAccount& acc) const;
/**
* This method writes this report to the DOM element @p e,
* within the DOM document @p doc.
*
* @param e The element which should be populated with info from this report
* @param doc The document which we can use to create new sub-elements
* if needed
* @param anonymous Whether the sensitive parts of the report should be
* masked
*/
void write(QDomElement& e, QDomDocument *doc, bool anonymous = false) const;
/**
* This method reads a report from the DOM element @p e, and
* populates this report with the results.
*
* @param e The element from which the report should be read
*
* @return bool True if a report was successfully loaded from the
* element @p e. If false is returned, the contents of this report
* object are undefined.
*/
bool read(const QDomElement& e);
/**
* This method creates a QDomElement for the @p document
* under the parent node @p parent. (This version overwrites the
* MMObject base class.)
*
* @param document reference to QDomDocument
* @param parent reference to QDomElement parent node
*/
virtual void writeXML(QDomDocument& document, QDomElement& parent) const;
/**
* This method checks if a reference to the given object exists. It returns,
* a @p true if the object is referencing the one requested by the
* parameter @p id. If it does not, this method returns @p false.
*
* @param id id of the object to be checked for references
* @retval true This object references object with id @p id.
* @retval false This object does not reference the object with id @p id.
*/
virtual bool hasReferenceTo(const QString& id) const;
/**
* This method allows to modify the default lineWidth for graphs.
* The default is 2.
*/
static void setLineWidth(int width);
/**
* Return row type as string.
*
* @param type type to get string for
* @return row type converted to string
*/
static QString toString(ERowType type);
/**
* Return report type as string.
*
* @param type report type to get string for
* @return report type converted to string
*/
static QString toString(EReportType type);
private:
/**
* The user-assigned name of the report
*/
QString m_name;
/**
* The user-assigned comment for the report, in case they want to make
* additional notes for themselves about the report.
*/
QString m_comment;
/**
* Where to group this report amongst the others in the UI view. This
* should be assigned by the UI system.
*/
QString m_group;
/**
* How much detail to show in the accounts
*/
enum EDetailLevel m_detailLevel;
/**
* Whether to sum: all, sold, bought or owned value
*/
enum EInvestmentSum m_investmentSum;
/**
* Whether to show transactions or just totals.
*/
bool m_hideTransactions;
/**
* Whether to convert all currencies to the base currency of the file (true).
* If this is false, it's up to the report generator to decide how to handle
* the currency.
*/
bool m_convertCurrency;
/**
* Whether this is one of the users' favorite reports
*/
bool m_favorite;
/**
* Whether this report should only include categories marked as "Tax"="Yes"
*/
bool m_tax;
/**
* Whether this report should only include investment accounts
*/
bool m_investments;
/**
* Whether this report should only include loan accounts
* Applies only to querytable reports. Mutually exclusive with
* m_investments.
*/
bool m_loans;
/**
* What sort of algorithm should be used to run the report
*/
enum EReportType m_reportType;
/**
* What sort of values should show up on the ROWS of this report
*/
enum ERowType m_rowType;
/**
* What sort of values should show up on the COLUMNS of this report,
* in the case of a 'PivotTable' report. Really this is used more as a
* QUANTITY of months or days. Whether it's months or days is determined
* by m_columnsAreDays.
*/
enum EColumnType m_columnType;
/**
* Whether the base unit of columns of this report is days. Only applies to
* 'PivotTable' reports. If false, then columns are months or multiples thereof.
*/
bool m_columnsAreDays;
/**
* What sort of values should show up on the COLUMNS of this report,
* in the case of a 'QueryTable' report
*/
enum EQueryColumns m_queryColumns;
/**
* The plain-language description of what the date range should be locked
* to. 'userDefined' means NO locking, in any other case, the report
* will be adjusted to match the date lock. So if the date lock is
* 'currentMonth', the start and end dates of the underlying filter will
* be updated to whatever the current month is. This updating happens
* automatically when the report is loaded, and should also be done
* manually by calling updateDateFilter() before generating the report
*/
dateOptionE m_dateLock;
/**
* Which account groups should be included in the report. This filter
* is applied to the individual splits AFTER a transaction has been
* matched using the underlying filter.
*/
- QList<MyMoneyAccount::accountTypeE> m_accountGroups;
+ QList<eMyMoney::Account> m_accountGroups;
/**
* Whether an account group filter has been set (see m_accountGroups)
*/
bool m_accountGroupFilter;
/**
* What format should be used to draw this report as a chart
*/
enum EChartType m_chartType;
/**
* Whether the value of individual data points should be drawn on the chart
*/
bool m_chartDataLabels;
/**
* Whether grid lines should be drawn on the chart
*/
bool m_chartCHGridLines;
bool m_chartSVGridLines;
/**
* Whether this report should be shown as a chart by default (otherwise it
* should be shown as a textual report)
*/
bool m_chartByDefault;
/**
* Width of the chart lines
*/
uint m_chartLineWidth;
/**
* Whether Y axis is logarithmic or linear
*/
bool m_logYaxis;
/**
* Y data range
*/
QString m_dataRangeStart;
QString m_dataRangeEnd;
/**
* Y data range division
*/
QString m_dataMajorTick;
QString m_dataMinorTick;
/**
* Y labels precision
*/
uint m_yLabelsPrecision;
/**
* Whether data range should be calculated automatically or is user defined
*/
dataOptionE m_dataLock;
/**
* Whether to include scheduled transactions
*/
bool m_includeSchedules;
/**
* Whether to include transfers. Only applies to Income/Expense reports
*/
bool m_includeTransfers;
/**
* The id of the budget associated with this report.
*/
QString m_budgetId;
/**
* Whether this report should print the actual data to go along with
* the budget. This is only valid if the report has a budget.
*/
bool m_includeBudgetActuals;
/**
* Whether this report should include all accounts and not only
* accounts with transactions.
*/
bool m_includeUnusedAccounts;
/**
* Whether this report should include columns for row totals
*/
bool m_showRowTotals;
/**
* Whether this report should include rows for column totals
*/
bool m_showColumnTotals;
/**
* Whether this report should include forecast balance
*/
bool m_includeForecast;
/**
* Whether this report should include moving average
*/
bool m_includeMovingAverage;
/**
* The amount of days that spans each moving average
*/
int m_movingAverageDays;
/**
* Whether this report should include prices
*/
bool m_includePrice;
/**
* Whether this report should include moving average prices
*/
bool m_includeAveragePrice;
/**
* Make the actual and forecast lines display as one
*/
bool m_mixedTime;
/**
* This stores the column for the current date
* This value is calculated dinamically and thus it is not saved in the file
*/
int m_currentDateColumn;
/**
* Time in days between the settlement date and the transaction date.
*/
uint m_settlementPeriod;
/**
* Controls showing short-term and long-term capital gains.
*/
bool m_showSTLTCapitalGains;
/**
* Date separating shot-term from long-term gains.
*/
QDate m_tseparator;
/**
* This member keeps the current setting for line graphs lineWidth.
* @sa setLineWidth()
*/
static int m_lineWidth;
/**
* This option is for investments reports only which
* show prices instead of balances as all other reports do.
* <p>
* Select this option to include prices for the given period (week, month,
* quarter, ...) only.
* </p>
* <p>
* If this option is off the last existing price is shown for a period, if
* it is on, in a table the value is '0' shown and in a chart a linear
* interpolation for the missing values will be performed.
* <br>Example:
* <br>There are prices for January and March, but there is no price for
* February.
* <ul>
* <li><b>OFF</b>: shows the price for February as the last price of
* January
* <li><b>ON</b>: in a table the value is '0', in a chart a linear
* interpolation for the February-price will be performed
* (so it makes a kind of average-value using the January- and the
* March-price in the chart)
* </ul>
* </p>
*/
bool m_skipZero;
static const QString getElName(const elNameE _el);
static const QString getAttrName(const attrNameE _attr);
};
/**
* Make it possible to hold @ref MyMoneyReport objects inside @ref QVariant objects.
*/
Q_DECLARE_METATYPE(MyMoneyReport)
#endif // MYMONEYREPORT_H
diff --git a/kmymoney/mymoney/mymoneyschedule.cpp b/kmymoney/mymoney/mymoneyschedule.cpp
index 5453027b4..2960c78ee 100644
--- a/kmymoney/mymoney/mymoneyschedule.cpp
+++ b/kmymoney/mymoney/mymoneyschedule.cpp
@@ -1,1408 +1,1409 @@
/***************************************************************************
mymoneyschedule.cpp
-------------------
copyright : (C) 2000-2002 by Michael Edwardes <mte@users.sourceforge.net>
(C) 2007 by Thomas Baumgart <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 "mymoneyschedule.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
#include <QMap>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyutils.h"
#include "mymoneyexception.h"
#include "mymoneyfile.h"
#include "imymoneyprocessingcalendar.h"
#include "mymoneystoragenames.h"
using namespace MyMoneyStorageNodes;
+using namespace eMyMoney;
static IMyMoneyProcessingCalendar* processingCalendarPtr = 0;
MyMoneySchedule::MyMoneySchedule() :
MyMoneyObject()
{
// Set up the default values
- m_occurrence = OCCUR_ANY;
+ m_occurrence = Schedule::Occurrence::Any;
m_occurrenceMultiplier = 1;
- m_type = TYPE_ANY;
- m_paymentType = STYPE_ANY;
+ m_type = Schedule::Type::Any;
+ m_paymentType = Schedule::PaymentType::Any;
m_fixed = false;
m_autoEnter = false;
m_startDate = QDate();
m_endDate = QDate();
m_lastPayment = QDate();
- m_weekendOption = MoveNothing;
+ m_weekendOption = Schedule::WeekendOption::MoveNothing;
}
-MyMoneySchedule::MyMoneySchedule(const QString& name, typeE type,
- occurrenceE occurrence, int occurrenceMultiplier,
- paymentTypeE paymentType,
+MyMoneySchedule::MyMoneySchedule(const QString& name, Schedule::Type type,
+ Schedule::Occurrence occurrence, int occurrenceMultiplier,
+ Schedule::PaymentType paymentType,
const QDate& /* startDate */,
const QDate& endDate,
bool fixed, bool autoEnter) :
MyMoneyObject()
{
// Set up the default values
m_name = name;
m_occurrence = occurrence;
m_occurrenceMultiplier = occurrenceMultiplier;
simpleToCompoundOccurrence(m_occurrenceMultiplier, m_occurrence);
m_type = type;
m_paymentType = paymentType;
m_fixed = fixed;
m_autoEnter = autoEnter;
m_startDate = QDate();
m_endDate = endDate;
m_lastPayment = QDate();
- m_weekendOption = MoveNothing;
+ m_weekendOption = Schedule::WeekendOption::MoveNothing;
}
MyMoneySchedule::MyMoneySchedule(const QDomElement& node) :
MyMoneyObject(node)
{
if (nodeNames[nnScheduleTX] != node.tagName())
throw MYMONEYEXCEPTION("Node was not SCHEDULED_TX");
m_name = node.attribute(getAttrName(anName));
m_startDate = stringToDate(node.attribute(getAttrName(anStartDate)));
m_endDate = stringToDate(node.attribute(getAttrName(anEndDate)));
m_lastPayment = stringToDate(node.attribute(getAttrName(anLastPayment)));
- m_type = static_cast<MyMoneySchedule::typeE>(node.attribute(getAttrName(anType)).toInt());
- m_paymentType = static_cast<MyMoneySchedule::paymentTypeE>(node.attribute(getAttrName(anPaymentType)).toInt());
- m_occurrence = static_cast<MyMoneySchedule::occurrenceE>(node.attribute(getAttrName(anOccurence)).toInt()); // krazy:exclude=spelling
+ m_type = static_cast<Schedule::Type>(node.attribute(getAttrName(anType)).toInt());
+ m_paymentType = static_cast<Schedule::PaymentType>(node.attribute(getAttrName(anPaymentType)).toInt());
+ m_occurrence = static_cast<Schedule::Occurrence>(node.attribute(getAttrName(anOccurence)).toInt()); // krazy:exclude=spelling
m_occurrenceMultiplier = node.attribute(getAttrName(anOccurenceMultiplier), "1").toInt(); // krazy:exclude=spelling
// Convert to compound occurrence
simpleToCompoundOccurrence(m_occurrenceMultiplier, m_occurrence);
m_autoEnter = static_cast<bool>(node.attribute(getAttrName(anAutoEnter)).toInt());
m_fixed = static_cast<bool>(node.attribute(getAttrName(anFixed)).toInt());
- m_weekendOption = static_cast<MyMoneySchedule::weekendOptionE>(node.attribute(getAttrName(anWeekendOption)).toInt());
+ m_weekendOption = static_cast<Schedule::WeekendOption>(node.attribute(getAttrName(anWeekendOption)).toInt());
// read in the associated transaction
QDomNodeList nodeList = node.elementsByTagName(nodeNames[nnTransaction]);
if (nodeList.count() == 0)
throw MYMONEYEXCEPTION("SCHEDULED_TX has no TRANSACTION node");
setTransaction(MyMoneyTransaction(nodeList.item(0).toElement(), false), true);
// some old versions did not remove the entry date and post date fields
// in the schedule. So if this is the case, we deal with a very old transaction
// and can't use the post date field as next due date. Hence, we wipe it out here
if (m_transaction.entryDate().isValid()) {
m_transaction.setPostDate(QDate());
m_transaction.setEntryDate(QDate());
}
// readin the recorded payments
nodeList = node.elementsByTagName(getElName(enPayments));
if (nodeList.count() > 0) {
nodeList = nodeList.item(0).toElement().elementsByTagName(getElName(enPayment));
for (int i = 0; i < nodeList.count(); ++i) {
m_recordedPayments << stringToDate(nodeList.item(i).toElement().attribute(getAttrName(anDate)));
}
}
// if the next due date is not set (comes from old version)
// then set it up the old way
if (!nextDueDate().isValid() && !m_lastPayment.isValid()) {
m_transaction.setPostDate(m_startDate);
// clear it, because the schedule has never been used
m_startDate = QDate();
}
// There are reports that lastPayment and nextDueDate are identical or
// that nextDueDate is older than lastPayment. This could
// be caused by older versions of the application. In this case, we just
// clear out the nextDueDate and let it calculate from the lastPayment.
if (nextDueDate().isValid() && nextDueDate() <= m_lastPayment) {
m_transaction.setPostDate(QDate());
}
if (!nextDueDate().isValid()) {
m_transaction.setPostDate(m_startDate);
m_transaction.setPostDate(nextPayment(m_lastPayment.addDays(1)));
}
}
MyMoneySchedule::MyMoneySchedule(const QString& id, const MyMoneySchedule& right) :
MyMoneyObject(id)
{
*this = right;
setId(id);
}
-MyMoneySchedule::occurrenceE MyMoneySchedule::occurrence() const
+Schedule::Occurrence MyMoneySchedule::occurrence() const
{
- MyMoneySchedule::occurrenceE occ = m_occurrence;
+ Schedule::Occurrence occ = m_occurrence;
int mult = m_occurrenceMultiplier;
compoundToSimpleOccurrence(mult, occ);
return occ;
}
void MyMoneySchedule::setStartDate(const QDate& date)
{
m_startDate = date;
}
-void MyMoneySchedule::setPaymentType(paymentTypeE type)
+void MyMoneySchedule::setPaymentType(Schedule::PaymentType type)
{
m_paymentType = type;
}
void MyMoneySchedule::setFixed(bool fixed)
{
m_fixed = fixed;
}
void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction)
{
setTransaction(transaction, false);
}
void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction, bool noDateCheck)
{
MyMoneyTransaction t = transaction;
if (!noDateCheck) {
// don't allow a transaction that has no due date
// if we get something like that, then we use the
// the current next due date. If that is also invalid
// we can't help it.
if (!t.postDate().isValid()) {
t.setPostDate(m_transaction.postDate());
}
if (!t.postDate().isValid())
return;
}
// make sure to clear out some unused information in scheduled transactions
// we need to do this for the case that the transaction passed as argument
// is a matched or imported transaction.
QList<MyMoneySplit> splits = t.splits();
if (splits.count() > 0) {
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) {
MyMoneySplit s = *it_s;
// clear out the bankID
if (!(*it_s).bankID().isEmpty()) {
s.setBankID(QString());
t.modifySplit(s);
}
// only clear payees from second split onwards
if (it_s == splits.constBegin())
continue;
if (!(*it_s).payeeId().isEmpty()) {
// but only if the split references an income/expense category
MyMoneyFile* file = MyMoneyFile::instance();
// some unit tests don't have a storage attached, so we
// simply skip the test
// Don't check for accounts with an id of 'Phony-ID' which is used
// internally for non-existing accounts (during creation of accounts)
if (file->storageAttached() && s.accountId() != QString("Phony-ID")) {
MyMoneyAccount acc = file->account(s.accountId());
if (acc.isIncomeExpense()) {
s.setPayeeId(QString());
t.modifySplit(s);
}
}
}
}
}
m_transaction = t;
// make sure that the transaction does not have an id so that we can enter
// it into the engine
m_transaction.clearId();
}
void MyMoneySchedule::setEndDate(const QDate& date)
{
m_endDate = date;
}
void MyMoneySchedule::setAutoEnter(bool autoenter)
{
m_autoEnter = autoenter;
}
const QDate& MyMoneySchedule::startDate() const
{
if (m_startDate.isValid())
return m_startDate;
return nextDueDate();
}
const QDate& MyMoneySchedule::nextDueDate() const
{
return m_transaction.postDate();
}
QDate MyMoneySchedule::adjustedNextDueDate() const
{
if (isFinished())
return QDate();
return adjustedDate(nextDueDate(), weekendOption());
}
-QDate MyMoneySchedule::adjustedDate(QDate date, weekendOptionE option) const
+QDate MyMoneySchedule::adjustedDate(QDate date, Schedule::WeekendOption option) const
{
- if (!date.isValid() || option == MyMoneySchedule::MoveNothing || isProcessingDate(date))
+ if (!date.isValid() || option == Schedule::WeekendOption::MoveNothing || isProcessingDate(date))
return date;
int step = 1;
- if (option == MyMoneySchedule::MoveBefore)
+ if (option == Schedule::WeekendOption::MoveBefore)
step = -1;
while (!isProcessingDate(date))
date = date.addDays(step);
return date;
}
void MyMoneySchedule::setNextDueDate(const QDate& date)
{
if (date.isValid()) {
m_transaction.setPostDate(date);
// m_startDate = date;
}
}
void MyMoneySchedule::setLastPayment(const QDate& date)
{
// Delete all payments older than date
QList<QDate>::Iterator it;
QList<QDate> delList;
for (it = m_recordedPayments.begin(); it != m_recordedPayments.end(); ++it) {
if (*it < date || !date.isValid())
delList.append(*it);
}
for (it = delList.begin(); it != delList.end(); ++it) {
m_recordedPayments.removeAll(*it);
}
m_lastPayment = date;
if (!m_startDate.isValid())
m_startDate = date;
}
void MyMoneySchedule::setName(const QString& nm)
{
m_name = nm;
}
-void MyMoneySchedule::setOccurrence(occurrenceE occ)
+void MyMoneySchedule::setOccurrence(Schedule::Occurrence occ)
{
- MyMoneySchedule::occurrenceE occ2 = occ;
+ Schedule::Occurrence occ2 = occ;
int mult = 1;
simpleToCompoundOccurrence(mult, occ2);
setOccurrencePeriod(occ2);
setOccurrenceMultiplier(mult);
}
-void MyMoneySchedule::setOccurrencePeriod(occurrenceE occ)
+void MyMoneySchedule::setOccurrencePeriod(Schedule::Occurrence occ)
{
m_occurrence = occ;
}
void MyMoneySchedule::setOccurrenceMultiplier(int occmultiplier)
{
m_occurrenceMultiplier = occmultiplier < 1 ? 1 : occmultiplier;
}
-void MyMoneySchedule::setType(typeE type)
+void MyMoneySchedule::setType(Schedule::Type type)
{
m_type = type;
}
void MyMoneySchedule::validate(bool id_check) const
{
/* Check the supplied instance is valid...
*
* To be valid it must not have the id set and have the following fields set:
*
* m_occurrence
* m_type
* m_startDate
* m_paymentType
* m_transaction
* the transaction must contain at least one split (two is better ;-) )
*/
if (id_check && !m_id.isEmpty())
throw MYMONEYEXCEPTION("ID for schedule not empty when required");
- if (m_occurrence == OCCUR_ANY)
+ if (m_occurrence == Schedule::Occurrence::Any)
throw MYMONEYEXCEPTION("Invalid occurrence type for schedule");
- if (m_type == TYPE_ANY)
+ if (m_type == Schedule::Type::Any)
throw MYMONEYEXCEPTION("Invalid type for schedule");
if (!nextDueDate().isValid())
throw MYMONEYEXCEPTION("Invalid next due date for schedule");
- if (m_paymentType == STYPE_ANY)
+ if (m_paymentType == Schedule::PaymentType::Any)
throw MYMONEYEXCEPTION("Invalid payment type for schedule");
if (m_transaction.splitCount() == 0)
throw MYMONEYEXCEPTION("Scheduled transaction does not contain splits");
// Check the payment types
switch (m_type) {
- case TYPE_BILL:
- if (m_paymentType == STYPE_DIRECTDEPOSIT || m_paymentType == STYPE_MANUALDEPOSIT)
+ case Schedule::Type::Bill:
+ if (m_paymentType == Schedule::PaymentType::DirectDeposit || m_paymentType == Schedule::PaymentType::ManualDeposit)
throw MYMONEYEXCEPTION("Invalid payment type for bills");
break;
- case TYPE_DEPOSIT:
- if (m_paymentType == STYPE_DIRECTDEBIT || m_paymentType == STYPE_WRITECHEQUE)
+ case Schedule::Type::Deposit:
+ if (m_paymentType == Schedule::PaymentType::DirectDebit || m_paymentType == Schedule::PaymentType::WriteChecque)
throw MYMONEYEXCEPTION("Invalid payment type for deposits");
break;
- case TYPE_ANY:
+ case Schedule::Type::Any:
throw MYMONEYEXCEPTION("Invalid type ANY");
break;
- case TYPE_TRANSFER:
-// if (m_paymentType == STYPE_DIRECTDEPOSIT || m_paymentType == STYPE_MANUALDEPOSIT)
+ case Schedule::Type::Transfer:
+// if (m_paymentType == DirectDeposit || m_paymentType == ManualDeposit)
// return false;
break;
- case TYPE_LOANPAYMENT:
+ case Schedule::Type::LoanPayment:
break;
}
}
QDate MyMoneySchedule::adjustedNextPayment(const QDate& refDate) const
{
return nextPaymentDate(true, refDate);
}
QDate MyMoneySchedule::nextPayment(const QDate& refDate) const
{
return nextPaymentDate(false, refDate);
}
QDate MyMoneySchedule::nextPaymentDate(const bool& adjust, const QDate& refDate) const
{
- weekendOptionE option(adjust ? weekendOption() :
- MyMoneySchedule::MoveNothing);
+ Schedule::WeekendOption option(adjust ? weekendOption() :
+ Schedule::WeekendOption::MoveNothing);
QDate adjEndDate(adjustedDate(m_endDate, option));
// if the enddate is valid and it is before the reference date,
// then there will be no more payments.
if (adjEndDate.isValid() && adjEndDate < refDate) {
return QDate();
}
QDate dueDate(nextDueDate());
QDate paymentDate(adjustedDate(dueDate, option));
if (paymentDate.isValid() &&
(paymentDate <= refDate || m_recordedPayments.contains(dueDate))) {
switch (m_occurrence) {
- case OCCUR_ONCE:
+ case Schedule::Occurrence::Once:
// If the lastPayment is already set or the payment should have been
// prior to the reference date then invalidate the payment date.
if (m_lastPayment.isValid() || paymentDate <= refDate)
paymentDate = QDate();
break;
- case OCCUR_DAILY: {
+ case Schedule::Occurrence::Daily: {
int step = m_occurrenceMultiplier;
do {
dueDate = dueDate.addDays(step);
paymentDate = adjustedDate(dueDate, option);
} while (paymentDate.isValid() &&
(paymentDate <= refDate ||
m_recordedPayments.contains(dueDate)));
}
break;
- case OCCUR_WEEKLY: {
+ case Schedule::Occurrence::Weekly: {
int step = 7 * m_occurrenceMultiplier;
do {
dueDate = dueDate.addDays(step);
paymentDate = adjustedDate(dueDate, option);
} while (paymentDate.isValid() &&
(paymentDate <= refDate ||
m_recordedPayments.contains(dueDate)));
}
break;
- case OCCUR_EVERYHALFMONTH:
+ case Schedule::Occurrence::EveryHalfMonth:
do {
dueDate = addHalfMonths(dueDate, m_occurrenceMultiplier);
paymentDate = adjustedDate(dueDate, option);
} while (paymentDate.isValid() &&
(paymentDate <= refDate ||
m_recordedPayments.contains(dueDate)));
break;
- case OCCUR_MONTHLY:
+ case Schedule::Occurrence::Monthly:
do {
dueDate = dueDate.addMonths(m_occurrenceMultiplier);
fixDate(dueDate);
paymentDate = adjustedDate(dueDate, option);
} while (paymentDate.isValid() &&
(paymentDate <= refDate ||
m_recordedPayments.contains(dueDate)));
break;
- case OCCUR_YEARLY:
+ case Schedule::Occurrence::Yearly:
do {
dueDate = dueDate.addYears(m_occurrenceMultiplier);
fixDate(dueDate);
paymentDate = adjustedDate(dueDate, option);
} while (paymentDate.isValid() &&
(paymentDate <= refDate ||
m_recordedPayments.contains(dueDate)));
break;
- case OCCUR_ANY:
+ case Schedule::Occurrence::Any:
default:
paymentDate = QDate();
break;
}
}
if (paymentDate.isValid() && adjEndDate.isValid() && paymentDate > adjEndDate)
paymentDate = QDate();
return paymentDate;
}
QList<QDate> MyMoneySchedule::paymentDates(const QDate& _startDate, const QDate& _endDate) const
{
QDate paymentDate(nextDueDate());
QList<QDate> theDates;
- weekendOptionE option(weekendOption());
+ Schedule::WeekendOption option(weekendOption());
QDate endDate(_endDate);
if (willEnd() && m_endDate < endDate) {
// consider the adjusted end date instead of the plain end date
endDate = adjustedDate(m_endDate, option);
}
QDate start_date(adjustedDate(startDate(), option));
// if the period specified by the parameters and the adjusted period
// defined for this schedule don't overlap, then the list remains empty
if ((willEnd() && adjustedDate(m_endDate, option) < _startDate)
|| start_date > endDate)
return theDates;
QDate date(adjustedDate(paymentDate, option));
switch (m_occurrence) {
- case OCCUR_ONCE:
+ case Schedule::Occurrence::Once:
if (start_date >= _startDate && start_date <= endDate)
theDates.append(start_date);
break;
- case OCCUR_DAILY:
+ case Schedule::Occurrence::Daily:
while (date.isValid() && (date <= endDate)) {
if (date >= _startDate)
theDates.append(date);
paymentDate = paymentDate.addDays(m_occurrenceMultiplier);
date = adjustedDate(paymentDate, option);
}
break;
- case OCCUR_WEEKLY: {
+ case Schedule::Occurrence::Weekly: {
int step = 7 * m_occurrenceMultiplier;
while (date.isValid() && (date <= endDate)) {
if (date >= _startDate)
theDates.append(date);
paymentDate = paymentDate.addDays(step);
date = adjustedDate(paymentDate, option);
}
}
break;
- case OCCUR_EVERYHALFMONTH:
+ case Schedule::Occurrence::EveryHalfMonth:
while (date.isValid() && (date <= endDate)) {
if (date >= _startDate)
theDates.append(date);
paymentDate = addHalfMonths(paymentDate, m_occurrenceMultiplier);
date = adjustedDate(paymentDate, option);
}
break;
- case OCCUR_MONTHLY:
+ case Schedule::Occurrence::Monthly:
while (date.isValid() && (date <= endDate)) {
if (date >= _startDate)
theDates.append(date);
paymentDate = paymentDate.addMonths(m_occurrenceMultiplier);
fixDate(paymentDate);
date = adjustedDate(paymentDate, option);
}
break;
- case OCCUR_YEARLY:
+ case Schedule::Occurrence::Yearly:
while (date.isValid() && (date <= endDate)) {
if (date >= _startDate)
theDates.append(date);
paymentDate = paymentDate.addYears(m_occurrenceMultiplier);
fixDate(paymentDate);
date = adjustedDate(paymentDate, option);
}
break;
- case OCCUR_ANY:
+ case Schedule::Occurrence::Any:
default:
break;
}
return theDates;
}
bool MyMoneySchedule::operator <(const MyMoneySchedule& right) const
{
return adjustedNextDueDate() < right.adjustedNextDueDate();
}
bool MyMoneySchedule::operator ==(const MyMoneySchedule& right) const
{
if (MyMoneyObject::operator==(right) &&
m_occurrence == right.m_occurrence &&
m_occurrenceMultiplier == right.m_occurrenceMultiplier &&
m_type == right.m_type &&
m_startDate == right.m_startDate &&
m_paymentType == right.m_paymentType &&
m_fixed == right.m_fixed &&
m_transaction == right.m_transaction &&
m_endDate == right.m_endDate &&
m_autoEnter == right.m_autoEnter &&
m_lastPayment == right.m_lastPayment &&
((m_name.length() == 0 && right.m_name.length() == 0) || (m_name == right.m_name)))
return true;
return false;
}
int MyMoneySchedule::transactionsRemaining() const
{
return transactionsRemainingUntil(adjustedDate(m_endDate, weekendOption()));
}
int MyMoneySchedule::transactionsRemainingUntil(const QDate& endDate) const
{
int counter = 0;
QDate startDate = m_lastPayment.isValid() ? m_lastPayment : m_startDate;
if (startDate.isValid() && endDate.isValid()) {
QList<QDate> dates = paymentDates(startDate, endDate);
counter = dates.count();
}
return counter;
}
MyMoneyAccount MyMoneySchedule::account(int cnt) const
{
QList<MyMoneySplit> splits = m_transaction.splits();
QList<MyMoneySplit>::ConstIterator it;
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount acc;
// search the first asset or liability account
for (it = splits.constBegin(); it != splits.constEnd() && (acc.id().isEmpty() || cnt); ++it) {
try {
acc = file->account((*it).accountId());
if (acc.isAssetLiability())
--cnt;
if (!cnt)
return acc;
} catch (const MyMoneyException &) {
qWarning("Schedule '%s' references unknown account '%s'", qPrintable(id()), qPrintable((*it).accountId()));
return MyMoneyAccount();
}
}
return MyMoneyAccount();
}
QDate MyMoneySchedule::dateAfter(int transactions) const
{
int counter = 1;
QDate paymentDate(startDate());
if (transactions <= 0)
return paymentDate;
switch (m_occurrence) {
- case OCCUR_ONCE:
+ case Schedule::Occurrence::Once:
break;
- case OCCUR_DAILY:
+ case Schedule::Occurrence::Daily:
while (counter++ < transactions)
paymentDate = paymentDate.addDays(m_occurrenceMultiplier);
break;
- case OCCUR_WEEKLY: {
+ case Schedule::Occurrence::Weekly: {
int step = 7 * m_occurrenceMultiplier;
while (counter++ < transactions)
paymentDate = paymentDate.addDays(step);
}
break;
- case OCCUR_EVERYHALFMONTH:
+ case Schedule::Occurrence::EveryHalfMonth:
paymentDate = addHalfMonths(paymentDate, m_occurrenceMultiplier * (transactions - 1));
break;
- case OCCUR_MONTHLY:
+ case Schedule::Occurrence::Monthly:
while (counter++ < transactions)
paymentDate = paymentDate.addMonths(m_occurrenceMultiplier);
break;
- case OCCUR_YEARLY:
+ case Schedule::Occurrence::Yearly:
while (counter++ < transactions)
paymentDate = paymentDate.addYears(m_occurrenceMultiplier);
break;
- case OCCUR_ANY:
+ case Schedule::Occurrence::Any:
default:
break;
}
return paymentDate;
}
bool MyMoneySchedule::isOverdue() const
{
if (isFinished())
return false;
if (adjustedNextDueDate() >= QDate::currentDate())
return false;
return true;
}
bool MyMoneySchedule::isFinished() const
{
if (!m_lastPayment.isValid())
return false;
if (m_endDate.isValid()) {
if (m_lastPayment >= m_endDate
|| !nextDueDate().isValid()
|| nextDueDate() > m_endDate)
return true;
}
// Check to see if its a once off payment
- if (m_occurrence == MyMoneySchedule::OCCUR_ONCE)
+ if (m_occurrence == Schedule::Occurrence::Once)
return true;
return false;
}
bool MyMoneySchedule::hasRecordedPayment(const QDate& date) const
{
// m_lastPayment should always be > recordedPayments()
if (m_lastPayment.isValid() && m_lastPayment >= date)
return true;
if (m_recordedPayments.contains(date))
return true;
return false;
}
void MyMoneySchedule::recordPayment(const QDate& date)
{
m_recordedPayments.append(date);
}
-void MyMoneySchedule::setWeekendOption(const weekendOptionE option)
+void MyMoneySchedule::setWeekendOption(const Schedule::WeekendOption option)
{
// make sure only valid values are used. Invalid defaults to MoveNothing.
switch (option) {
- case MoveBefore:
- case MoveAfter:
+ case Schedule::WeekendOption::MoveBefore:
+ case Schedule::WeekendOption::MoveAfter:
m_weekendOption = option;
break;
default:
- m_weekendOption = MoveNothing;
+ m_weekendOption = Schedule::WeekendOption::MoveNothing;
break;
}
}
void MyMoneySchedule::fixDate(QDate& date) const
{
QDate fixDate(m_startDate);
if (fixDate.isValid()
&& date.day() != fixDate.day()
&& QDate::isValid(date.year(), date.month(), fixDate.day())) {
date = QDate(date.year(), date.month(), fixDate.day());
}
}
void MyMoneySchedule::writeXML(QDomDocument& document, QDomElement& parent) const
{
QDomElement el = document.createElement(nodeNames[nnScheduleTX]);
writeBaseXML(document, el);
el.setAttribute(getAttrName(anName), m_name);
- el.setAttribute(getAttrName(anType), m_type);
- el.setAttribute(getAttrName(anOccurence), m_occurrence); // krazy:exclude=spelling
+ el.setAttribute(getAttrName(anType), (int)m_type);
+ el.setAttribute(getAttrName(anOccurence), (int)m_occurrence); // krazy:exclude=spelling
el.setAttribute(getAttrName(anOccurenceMultiplier), m_occurrenceMultiplier);
- el.setAttribute(getAttrName(anPaymentType), m_paymentType);
+ el.setAttribute(getAttrName(anPaymentType), (int)m_paymentType);
el.setAttribute(getAttrName(anStartDate), dateToString(m_startDate));
el.setAttribute(getAttrName(anEndDate), dateToString(m_endDate));
el.setAttribute(getAttrName(anFixed), m_fixed);
el.setAttribute(getAttrName(anAutoEnter), m_autoEnter);
el.setAttribute(getAttrName(anLastPayment), dateToString(m_lastPayment));
- el.setAttribute(getAttrName(anWeekendOption), m_weekendOption);
+ el.setAttribute(getAttrName(anWeekendOption), (int)m_weekendOption);
//store the payment history for this scheduled task.
QList<QDate> payments = recordedPayments();
QList<QDate>::ConstIterator it;
QDomElement paymentsElement = document.createElement(getElName(enPayments));
for (it = payments.constBegin(); it != payments.constEnd(); ++it) {
QDomElement paymentEntry = document.createElement(getElName(enPayment));
paymentEntry.setAttribute(getAttrName(anDate), dateToString(*it));
paymentsElement.appendChild(paymentEntry);
}
el.appendChild(paymentsElement);
//store the transaction data for this task.
m_transaction.writeXML(document, el);
parent.appendChild(el);
}
bool MyMoneySchedule::hasReferenceTo(const QString& id) const
{
return m_transaction.hasReferenceTo(id);
}
QString MyMoneySchedule::occurrenceToString() const
{
return occurrenceToString(occurrenceMultiplier(), occurrencePeriod());
}
-QString MyMoneySchedule::occurrenceToString(occurrenceE occurrence)
+QString MyMoneySchedule::occurrenceToString(Schedule::Occurrence occurrence)
{
QString occurrenceString = I18N_NOOP2("Frequency of schedule", "Any");
- if (occurrence == MyMoneySchedule::OCCUR_ONCE)
+ if (occurrence == Schedule::Occurrence::Once)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Once");
- else if (occurrence == MyMoneySchedule::OCCUR_DAILY)
+ else if (occurrence == Schedule::Occurrence::Daily)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Daily");
- else if (occurrence == MyMoneySchedule::OCCUR_WEEKLY)
+ else if (occurrence == Schedule::Occurrence::Weekly)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Weekly");
- else if (occurrence == MyMoneySchedule::OCCUR_FORTNIGHTLY)
+ else if (occurrence == Schedule::Occurrence::Fortnightly)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Fortnightly");
- else if (occurrence == MyMoneySchedule::OCCUR_EVERYOTHERWEEK)
+ else if (occurrence == Schedule::Occurrence::EveryOtherWeek)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other week");
- else if (occurrence == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
+ else if (occurrence == Schedule::Occurrence::EveryHalfMonth)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every half month");
- else if (occurrence == MyMoneySchedule::OCCUR_EVERYTHREEWEEKS)
+ else if (occurrence == Schedule::Occurrence::EveryThreeWeeks)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three weeks");
- else if (occurrence == MyMoneySchedule::OCCUR_EVERYFOURWEEKS)
+ else if (occurrence == Schedule::Occurrence::EveryFourWeeks)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four weeks");
- else if (occurrence == MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS)
+ else if (occurrence == Schedule::Occurrence::EveryThirtyDays)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every thirty days");
- else if (occurrence == MyMoneySchedule::OCCUR_MONTHLY)
+ else if (occurrence == Schedule::Occurrence::Monthly)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Monthly");
- else if (occurrence == MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS)
+ else if (occurrence == Schedule::Occurrence::EveryEightWeeks)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every eight weeks");
- else if (occurrence == MyMoneySchedule::OCCUR_EVERYOTHERMONTH)
+ else if (occurrence == Schedule::Occurrence::EveryOtherMonth)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every two months");
- else if (occurrence == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS)
+ else if (occurrence == Schedule::Occurrence::EveryThreeMonths)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three months");
- else if (occurrence == MyMoneySchedule::OCCUR_QUARTERLY)
+ else if (occurrence == Schedule::Occurrence::Quarterly)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Quarterly");
- else if (occurrence == MyMoneySchedule::OCCUR_EVERYFOURMONTHS)
+ else if (occurrence == Schedule::Occurrence::EveryFourMonths)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four months");
- else if (occurrence == MyMoneySchedule::OCCUR_TWICEYEARLY)
+ else if (occurrence == Schedule::Occurrence::TwiceYearly)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Twice yearly");
- else if (occurrence == MyMoneySchedule::OCCUR_YEARLY)
+ else if (occurrence == Schedule::Occurrence::Yearly)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Yearly");
- else if (occurrence == MyMoneySchedule::OCCUR_EVERYOTHERYEAR)
+ else if (occurrence == Schedule::Occurrence::EveryOtherYear)
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other year");
return occurrenceString;
}
-QString MyMoneySchedule::occurrenceToString(int mult, occurrenceE type)
+QString MyMoneySchedule::occurrenceToString(int mult, Schedule::Occurrence type)
{
QString occurrenceString = I18N_NOOP2("Frequency of schedule", "Any");
- if (type == MyMoneySchedule::OCCUR_ONCE)
+ if (type == Schedule::Occurrence::Once)
switch (mult) {
case 1:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Once");
break;
default:
occurrenceString = I18N_NOOP2("Frequency of schedule", QString("%1 times").arg(mult));
}
- else if (type == MyMoneySchedule::OCCUR_DAILY)
+ else if (type == Schedule::Occurrence::Daily)
switch (mult) {
case 1:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Daily");
break;
case 30:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every thirty days");
break;
default:
occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 days").arg(mult));
}
- else if (type == MyMoneySchedule::OCCUR_WEEKLY)
+ else if (type == Schedule::Occurrence::Weekly)
switch (mult) {
case 1:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Weekly");
break;
case 2:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other week");
break;
case 3:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three weeks");
break;
case 4:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four weeks");
break;
case 8:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every eight weeks");
break;
default:
occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 weeks").arg(mult));
}
- else if (type == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
+ else if (type == Schedule::Occurrence::EveryHalfMonth)
switch (mult) {
case 1:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every half month");
break;
default:
occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 half months").arg(mult));
}
- else if (type == MyMoneySchedule::OCCUR_MONTHLY)
+ else if (type == Schedule::Occurrence::Monthly)
switch (mult) {
case 1:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Monthly");
break;
case 2:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every two months");
break;
case 3:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three months");
break;
case 4:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four months");
break;
case 6:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Twice yearly");
break;
default:
occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 months").arg(mult));
}
- else if (type == MyMoneySchedule::OCCUR_YEARLY)
+ else if (type == Schedule::Occurrence::Yearly)
switch (mult) {
case 1:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Yearly");
break;
case 2:
occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other year");
break;
default:
occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 years").arg(mult));
}
return occurrenceString;
}
-QString MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::occurrenceE type)
+QString MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence type)
{
QString occurrenceString = I18N_NOOP2("Schedule occurrence period", "Any");
- if (type == MyMoneySchedule::OCCUR_ONCE)
+ if (type == Schedule::Occurrence::Once)
occurrenceString = I18N_NOOP2("Schedule occurrence period", "Once");
- else if (type == MyMoneySchedule::OCCUR_DAILY)
+ else if (type == Schedule::Occurrence::Daily)
occurrenceString = I18N_NOOP2("Schedule occurrence period", "Day");
- else if (type == MyMoneySchedule::OCCUR_WEEKLY)
+ else if (type == Schedule::Occurrence::Weekly)
occurrenceString = I18N_NOOP2("Schedule occurrence period", "Week");
- else if (type == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
+ else if (type == Schedule::Occurrence::EveryHalfMonth)
occurrenceString = I18N_NOOP2("Schedule occurrence period", "Half-month");
- else if (type == MyMoneySchedule::OCCUR_MONTHLY)
+ else if (type == Schedule::Occurrence::Monthly)
occurrenceString = I18N_NOOP2("Schedule occurrence period", "Month");
- else if (type == MyMoneySchedule::OCCUR_YEARLY)
+ else if (type == Schedule::Occurrence::Yearly)
occurrenceString = I18N_NOOP2("Schedule occurrence period", "Year");
return occurrenceString;
}
-QString MyMoneySchedule::scheduleTypeToString(MyMoneySchedule::typeE type)
+QString MyMoneySchedule::scheduleTypeToString(Schedule::Type type)
{
QString text;
switch (type) {
- case MyMoneySchedule::TYPE_BILL:
+ case Schedule::Type::Bill:
text = I18N_NOOP2("Scheduled transaction type", "Bill");
break;
- case MyMoneySchedule::TYPE_DEPOSIT:
+ case Schedule::Type::Deposit:
text = I18N_NOOP2("Scheduled transaction type", "Deposit");
break;
- case MyMoneySchedule::TYPE_TRANSFER:
+ case Schedule::Type::Transfer:
text = I18N_NOOP2("Scheduled transaction type", "Transfer");
break;
- case MyMoneySchedule::TYPE_LOANPAYMENT:
+ case Schedule::Type::LoanPayment:
text = I18N_NOOP2("Scheduled transaction type", "Loan payment");
break;
- case MyMoneySchedule::TYPE_ANY:
+ case Schedule::Type::Any:
default:
text = I18N_NOOP2("Scheduled transaction type", "Unknown");
}
return text;
}
-QString MyMoneySchedule::paymentMethodToString(MyMoneySchedule::paymentTypeE paymentType)
+QString MyMoneySchedule::paymentMethodToString(Schedule::PaymentType paymentType)
{
QString text;
switch (paymentType) {
- case MyMoneySchedule::STYPE_DIRECTDEBIT:
+ case Schedule::PaymentType::DirectDebit:
text = I18N_NOOP2("Scheduled Transaction payment type", "Direct debit");
break;
- case MyMoneySchedule::STYPE_DIRECTDEPOSIT:
+ case Schedule::PaymentType::DirectDeposit:
text = I18N_NOOP2("Scheduled Transaction payment type", "Direct deposit");
break;
- case MyMoneySchedule::STYPE_MANUALDEPOSIT:
+ case Schedule::PaymentType::ManualDeposit:
text = I18N_NOOP2("Scheduled Transaction payment type", "Manual deposit");
break;
- case MyMoneySchedule::STYPE_OTHER:
+ case Schedule::PaymentType::Other:
text = I18N_NOOP2("Scheduled Transaction payment type", "Other");
break;
- case MyMoneySchedule::STYPE_WRITECHEQUE:
+ case Schedule::PaymentType::WriteChecque:
text = I18N_NOOP2("Scheduled Transaction payment type", "Write check");
break;
- case MyMoneySchedule::STYPE_STANDINGORDER:
+ case Schedule::PaymentType::StandingOrder:
text = I18N_NOOP2("Scheduled Transaction payment type", "Standing order");
break;
- case MyMoneySchedule::STYPE_BANKTRANSFER:
+ case Schedule::PaymentType::BankTransfer:
text = I18N_NOOP2("Scheduled Transaction payment type", "Bank transfer");
break;
- case MyMoneySchedule::STYPE_ANY:
+ case Schedule::PaymentType::Any:
text = I18N_NOOP2("Scheduled Transaction payment type", "Any (Error)");
break;
}
return text;
}
-QString MyMoneySchedule::weekendOptionToString(MyMoneySchedule::weekendOptionE weekendOption)
+QString MyMoneySchedule::weekendOptionToString(Schedule::WeekendOption weekendOption)
{
QString text;
switch (weekendOption) {
- case MyMoneySchedule::MoveBefore:
+ case Schedule::WeekendOption::MoveBefore:
text = I18N_NOOP("Change the date to the previous processing day");
break;
- case MyMoneySchedule::MoveAfter:
+ case Schedule::WeekendOption::MoveAfter:
text = I18N_NOOP("Change the date to the next processing day");
break;
- case MyMoneySchedule::MoveNothing:
+ case Schedule::WeekendOption::MoveNothing:
text = I18N_NOOP("Do Nothing");
break;
}
return text;
}
// until we don't have the means to store the value
// of the variation, we default to 10% in case this
// scheduled transaction is marked 'not fixed'.
//
// ipwizard 2009-04-18
int MyMoneySchedule::variation() const
{
int rc = 0;
if (!isFixed()) {
rc = 10;
#if 0
QString var = value("kmm-variation");
if (!var.isEmpty())
rc = var.toInt();
#endif
}
return rc;
}
void MyMoneySchedule::setVariation(int var)
{
Q_UNUSED(var)
#if 0
deletePair("kmm-variation");
if (var != 0)
setValue("kmm-variation", QString("%1").arg(var));
#endif
}
-int MyMoneySchedule::eventsPerYear(MyMoneySchedule::occurrenceE occurrence)
+int MyMoneySchedule::eventsPerYear(Schedule::Occurrence occurrence)
{
int rc = 0;
switch (occurrence) {
- case MyMoneySchedule::OCCUR_DAILY:
+ case Schedule::Occurrence::Daily:
rc = 365;
break;
- case MyMoneySchedule::OCCUR_WEEKLY:
+ case Schedule::Occurrence::Weekly:
rc = 52;
break;
- case MyMoneySchedule::OCCUR_FORTNIGHTLY:
+ case Schedule::Occurrence::Fortnightly:
rc = 26;
break;
- case MyMoneySchedule::OCCUR_EVERYOTHERWEEK:
+ case Schedule::Occurrence::EveryOtherWeek:
rc = 26;
break;
- case MyMoneySchedule::OCCUR_EVERYHALFMONTH:
+ case Schedule::Occurrence::EveryHalfMonth:
rc = 24;
break;
- case MyMoneySchedule::OCCUR_EVERYTHREEWEEKS:
+ case Schedule::Occurrence::EveryThreeWeeks:
rc = 17;
break;
- case MyMoneySchedule::OCCUR_EVERYFOURWEEKS:
+ case Schedule::Occurrence::EveryFourWeeks:
rc = 13;
break;
- case MyMoneySchedule::OCCUR_MONTHLY:
- case MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS:
+ case Schedule::Occurrence::Monthly:
+ case Schedule::Occurrence::EveryThirtyDays:
rc = 12;
break;
- case MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS:
+ case Schedule::Occurrence::EveryEightWeeks:
rc = 6;
break;
- case MyMoneySchedule::OCCUR_EVERYOTHERMONTH:
+ case Schedule::Occurrence::EveryOtherMonth:
rc = 6;
break;
- case MyMoneySchedule::OCCUR_EVERYTHREEMONTHS:
- case MyMoneySchedule::OCCUR_QUARTERLY:
+ case Schedule::Occurrence::EveryThreeMonths:
+ case Schedule::Occurrence::Quarterly:
rc = 4;
break;
- case MyMoneySchedule::OCCUR_EVERYFOURMONTHS:
+ case Schedule::Occurrence::EveryFourMonths:
rc = 3;
break;
- case MyMoneySchedule::OCCUR_TWICEYEARLY:
+ case Schedule::Occurrence::TwiceYearly:
rc = 2;
break;
- case MyMoneySchedule::OCCUR_YEARLY:
+ case Schedule::Occurrence::Yearly:
rc = 1;
break;
default:
qWarning("Occurrence not supported by financial calculator");
}
return rc;
}
-int MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::occurrenceE occurrence)
+int MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence occurrence)
{
int rc = 0;
switch (occurrence) {
- case MyMoneySchedule::OCCUR_DAILY:
+ case Schedule::Occurrence::Daily:
rc = 1;
break;
- case MyMoneySchedule::OCCUR_WEEKLY:
+ case Schedule::Occurrence::Weekly:
rc = 7;
break;
- case MyMoneySchedule::OCCUR_FORTNIGHTLY:
+ case Schedule::Occurrence::Fortnightly:
rc = 14;
break;
- case MyMoneySchedule::OCCUR_EVERYOTHERWEEK:
+ case Schedule::Occurrence::EveryOtherWeek:
rc = 14;
break;
- case MyMoneySchedule::OCCUR_EVERYHALFMONTH:
+ case Schedule::Occurrence::EveryHalfMonth:
rc = 15;
break;
- case MyMoneySchedule::OCCUR_EVERYTHREEWEEKS:
+ case Schedule::Occurrence::EveryThreeWeeks:
rc = 21;
break;
- case MyMoneySchedule::OCCUR_EVERYFOURWEEKS:
+ case Schedule::Occurrence::EveryFourWeeks:
rc = 28;
break;
- case MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS:
+ case Schedule::Occurrence::EveryThirtyDays:
rc = 30;
break;
- case MyMoneySchedule::OCCUR_MONTHLY:
+ case Schedule::Occurrence::Monthly:
rc = 30;
break;
- case MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS:
+ case Schedule::Occurrence::EveryEightWeeks:
rc = 56;
break;
- case MyMoneySchedule::OCCUR_EVERYOTHERMONTH:
+ case Schedule::Occurrence::EveryOtherMonth:
rc = 60;
break;
- case MyMoneySchedule::OCCUR_EVERYTHREEMONTHS:
- case MyMoneySchedule::OCCUR_QUARTERLY:
+ case Schedule::Occurrence::EveryThreeMonths:
+ case Schedule::Occurrence::Quarterly:
rc = 90;
break;
- case MyMoneySchedule::OCCUR_EVERYFOURMONTHS:
+ case Schedule::Occurrence::EveryFourMonths:
rc = 120;
break;
- case MyMoneySchedule::OCCUR_TWICEYEARLY:
+ case Schedule::Occurrence::TwiceYearly:
rc = 180;
break;
- case MyMoneySchedule::OCCUR_YEARLY:
+ case Schedule::Occurrence::Yearly:
rc = 360;
break;
default:
qWarning("Occurrence not supported by financial calculator");
}
return rc;
}
QDate MyMoneySchedule::addHalfMonths(QDate date, int mult) const
{
QDate newdate = date;
int d, dm;
if (mult > 0) {
d = newdate.day();
if (d <= 12) {
if (mult % 2 == 0)
newdate = newdate.addMonths(mult >> 1);
else
newdate = newdate.addMonths(mult >> 1).addDays(15);
} else
for (int i = 0; i < mult; i++) {
if (d <= 13)
newdate = newdate.addDays(15);
else {
dm = newdate.daysInMonth();
if (d == 14)
newdate = newdate.addDays((dm < 30) ? dm - d : 15);
else if (d == 15)
newdate = newdate.addDays(dm - d);
else if (d == dm)
newdate = newdate.addDays(15 - d).addMonths(1);
else
newdate = newdate.addDays(-15).addMonths(1);
}
d = newdate.day();
}
} else if (mult < 0) // Go backwards
for (int i = 0; i > mult; i--) {
d = newdate.day();
dm = newdate.daysInMonth();
if (d > 15) {
dm = newdate.daysInMonth();
newdate = newdate.addDays((d == dm) ? 15 - dm : -15);
} else if (d <= 13)
newdate = newdate.addMonths(-1).addDays(15);
else if (d == 15)
newdate = newdate.addDays(-15);
else { // 14
newdate = newdate.addMonths(-1);
dm = newdate.daysInMonth();
newdate = newdate.addDays((dm < 30) ? dm - d : 15);
}
}
return newdate;
}
/**
* Helper method to convert simple occurrence to compound occurrence + multiplier
*
* @param multiplier Returned by reference. Adjusted multiplier
* @param occurrence Returned by reference. Occurrence type
*/
-void MyMoneySchedule::simpleToCompoundOccurrence(int& multiplier, occurrenceE& occurrence)
+void MyMoneySchedule::simpleToCompoundOccurrence(int& multiplier, Schedule::Occurrence& occurrence)
{
- occurrenceE newOcc = occurrence;
+ Schedule::Occurrence newOcc = occurrence;
int newMulti = 1;
- if (occurrence == MyMoneySchedule::OCCUR_ONCE ||
- occurrence == MyMoneySchedule::OCCUR_DAILY ||
- occurrence == MyMoneySchedule::OCCUR_WEEKLY ||
- occurrence == MyMoneySchedule::OCCUR_EVERYHALFMONTH ||
- occurrence == MyMoneySchedule::OCCUR_MONTHLY ||
- occurrence == MyMoneySchedule::OCCUR_YEARLY) { // Already a base occurrence and multiplier
- } else if (occurrence == MyMoneySchedule::OCCUR_FORTNIGHTLY ||
- occurrence == MyMoneySchedule::OCCUR_EVERYOTHERWEEK) {
- newOcc = MyMoneySchedule::OCCUR_WEEKLY;
+ if (occurrence == Schedule::Occurrence::Once ||
+ occurrence == Schedule::Occurrence::Daily ||
+ occurrence == Schedule::Occurrence::Weekly ||
+ occurrence == Schedule::Occurrence::EveryHalfMonth ||
+ occurrence == Schedule::Occurrence::Monthly ||
+ occurrence == Schedule::Occurrence::Yearly) { // Already a base occurrence and multiplier
+ } else if (occurrence == Schedule::Occurrence::Fortnightly ||
+ occurrence == Schedule::Occurrence::EveryOtherWeek) {
+ newOcc = Schedule::Occurrence::Weekly;
newMulti = 2;
- } else if (occurrence == MyMoneySchedule::OCCUR_EVERYTHREEWEEKS) {
- newOcc = MyMoneySchedule::OCCUR_WEEKLY;
+ } else if (occurrence == Schedule::Occurrence::EveryThreeWeeks) {
+ newOcc = Schedule::Occurrence::Weekly;
newMulti = 3;
- } else if (occurrence == MyMoneySchedule::OCCUR_EVERYFOURWEEKS) {
- newOcc = MyMoneySchedule::OCCUR_WEEKLY;
+ } else if (occurrence == Schedule::Occurrence::EveryFourWeeks) {
+ newOcc = Schedule::Occurrence::Weekly;
newMulti = 4;
- } else if (occurrence == MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS) {
- newOcc = MyMoneySchedule::OCCUR_DAILY;
+ } else if (occurrence == Schedule::Occurrence::EveryThirtyDays) {
+ newOcc = Schedule::Occurrence::Daily;
newMulti = 30;
- } else if (occurrence == MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS) {
- newOcc = MyMoneySchedule::OCCUR_WEEKLY;
+ } else if (occurrence == Schedule::Occurrence::EveryEightWeeks) {
+ newOcc = Schedule::Occurrence::Weekly;
newMulti = 8;
- } else if (occurrence == MyMoneySchedule::OCCUR_EVERYOTHERMONTH) {
- newOcc = MyMoneySchedule::OCCUR_MONTHLY;
+ } else if (occurrence == Schedule::Occurrence::EveryOtherMonth) {
+ newOcc = Schedule::Occurrence::Monthly;
newMulti = 2;
- } else if (occurrence == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS ||
- occurrence == MyMoneySchedule::OCCUR_QUARTERLY) {
- newOcc = MyMoneySchedule::OCCUR_MONTHLY;
+ } else if (occurrence == Schedule::Occurrence::EveryThreeMonths ||
+ occurrence == Schedule::Occurrence::Quarterly) {
+ newOcc = Schedule::Occurrence::Monthly;
newMulti = 3;
- } else if (occurrence == MyMoneySchedule::OCCUR_EVERYFOURMONTHS) {
- newOcc = MyMoneySchedule::OCCUR_MONTHLY;
+ } else if (occurrence == Schedule::Occurrence::EveryFourMonths) {
+ newOcc = Schedule::Occurrence::Monthly;
newMulti = 4;
- } else if (occurrence == MyMoneySchedule::OCCUR_TWICEYEARLY) {
- newOcc = MyMoneySchedule::OCCUR_MONTHLY;
+ } else if (occurrence == Schedule::Occurrence::TwiceYearly) {
+ newOcc = Schedule::Occurrence::Monthly;
newMulti = 6;
- } else if (occurrence == MyMoneySchedule::OCCUR_EVERYOTHERYEAR) {
- newOcc = MyMoneySchedule::OCCUR_YEARLY;
+ } else if (occurrence == Schedule::Occurrence::EveryOtherYear) {
+ newOcc = Schedule::Occurrence::Yearly;
newMulti = 2;
} else { // Unknown
- newOcc = MyMoneySchedule::OCCUR_ANY;
+ newOcc = Schedule::Occurrence::Any;
newMulti = 1;
}
if (newOcc != occurrence) {
occurrence = newOcc;
multiplier = newMulti == 1 ? multiplier : newMulti * multiplier;
}
}
/**
* Helper method to convert compound occurrence + multiplier to simple occurrence
*
* @param multiplier Returned by reference. Adjusted multiplier
* @param occurrence Returned by reference. Occurrence type
*/
-void MyMoneySchedule::compoundToSimpleOccurrence(int& multiplier, occurrenceE& occurrence)
+void MyMoneySchedule::compoundToSimpleOccurrence(int& multiplier, Schedule::Occurrence& occurrence)
{
- occurrenceE newOcc = occurrence;
- if (occurrence == MyMoneySchedule::OCCUR_ONCE) { // Nothing to do
- } else if (occurrence == MyMoneySchedule::OCCUR_DAILY) {
+ Schedule::Occurrence newOcc = occurrence;
+ if (occurrence == Schedule::Occurrence::Once) { // Nothing to do
+ } else if (occurrence == Schedule::Occurrence::Daily) {
switch (multiplier) {
case 1:
break;
case 30:
- newOcc = MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS;
+ newOcc = Schedule::Occurrence::EveryThirtyDays;
break;
}
- } else if (newOcc == MyMoneySchedule::OCCUR_WEEKLY) {
+ } else if (newOcc == Schedule::Occurrence::Weekly) {
switch (multiplier) {
case 1:
break;
case 2:
- newOcc = MyMoneySchedule::OCCUR_EVERYOTHERWEEK;
+ newOcc = Schedule::Occurrence::EveryOtherWeek;
break;
case 3:
- newOcc = MyMoneySchedule::OCCUR_EVERYTHREEWEEKS;
+ newOcc = Schedule::Occurrence::EveryThreeWeeks;
break;
case 4:
- newOcc = MyMoneySchedule::OCCUR_EVERYFOURWEEKS;
+ newOcc = Schedule::Occurrence::EveryFourWeeks;
break;
case 8:
- newOcc = MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS;
+ newOcc = Schedule::Occurrence::EveryEightWeeks;
break;
}
- } else if (occurrence == MyMoneySchedule::OCCUR_MONTHLY)
+ } else if (occurrence == Schedule::Occurrence::Monthly)
switch (multiplier) {
case 1:
break;
case 2:
- newOcc = MyMoneySchedule::OCCUR_EVERYOTHERMONTH;
+ newOcc = Schedule::Occurrence::EveryOtherMonth;
break;
case 3:
- newOcc = MyMoneySchedule::OCCUR_EVERYTHREEMONTHS;
+ newOcc = Schedule::Occurrence::EveryThreeMonths;
break;
case 4:
- newOcc = MyMoneySchedule::OCCUR_EVERYFOURMONTHS;
+ newOcc = Schedule::Occurrence::EveryFourMonths;
break;
case 6:
- newOcc = MyMoneySchedule::OCCUR_TWICEYEARLY;
+ newOcc = Schedule::Occurrence::TwiceYearly;
break;
}
- else if (occurrence == MyMoneySchedule::OCCUR_EVERYHALFMONTH)
+ else if (occurrence == Schedule::Occurrence::EveryHalfMonth)
switch (multiplier) {
case 1:
break;
}
- else if (occurrence == MyMoneySchedule::OCCUR_YEARLY) {
+ else if (occurrence == Schedule::Occurrence::Yearly) {
switch (multiplier) {
case 1:
break;
case 2:
- newOcc = MyMoneySchedule::OCCUR_EVERYOTHERYEAR;
+ newOcc = Schedule::Occurrence::EveryOtherYear;
break;
}
}
if (occurrence != newOcc) { // Changed to derived type
occurrence = newOcc;
multiplier = 1;
}
}
void MyMoneySchedule::setProcessingCalendar(IMyMoneyProcessingCalendar* pc)
{
processingCalendarPtr = pc;
}
bool MyMoneySchedule::isProcessingDate(const QDate& date) const
{
if (processingCalendarPtr)
return processingCalendarPtr->isProcessingDate(date);
return date.dayOfWeek() < Qt::Saturday;
}
IMyMoneyProcessingCalendar* MyMoneySchedule::processingCalendar() const
{
return processingCalendarPtr;
}
bool MyMoneySchedule::replaceId(const QString& newId, const QString& oldId)
{
return m_transaction.replaceId(newId, oldId);
}
const QString MyMoneySchedule::getElName(const elNameE _el)
{
static const QMap<elNameE, QString> elNames = {
{enPayment, QStringLiteral("PAYMENT")},
{enPayments, QStringLiteral("PAYMENTS")}
};
return elNames[_el];
}
const QString MyMoneySchedule::getAttrName(const attrNameE _attr)
{
static const QHash<attrNameE, QString> attrNames = {
{anName, QStringLiteral("name")},
{anType, QStringLiteral("type")},
{anOccurence, QStringLiteral("occurence")},
{anOccurenceMultiplier, QStringLiteral("occurenceMultiplier")},
{anPaymentType, QStringLiteral("paymentType")},
{anFixed, QStringLiteral("fixed")},
{anAutoEnter, QStringLiteral("autoEnter")},
{anLastPayment, QStringLiteral("lastPayment")},
{anWeekendOption, QStringLiteral("weekendOption")},
{anDate, QStringLiteral("date")},
{anStartDate, QStringLiteral("startDate")},
{anEndDate, QStringLiteral("endDate")}
};
return attrNames[_attr];
}
diff --git a/kmymoney/mymoney/mymoneyschedule.h b/kmymoney/mymoney/mymoneyschedule.h
index 7056cc926..7afa89544 100644
--- a/kmymoney/mymoney/mymoneyschedule.h
+++ b/kmymoney/mymoney/mymoneyschedule.h
@@ -1,791 +1,752 @@
/***************************************************************************
mymoneyschedule.h
-------------------
copyright : (C) 2000-2002 by Michael Edwardes <mte@users.sourceforge.net>
(C) 2007 by Thomas Baumgart <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 MYMONEYSCHEDULED_H
#define MYMONEYSCHEDULED_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneytransaction.h"
#include "mymoneyaccount.h"
#include "kmm_mymoney_export.h"
#include "mymoneyunittestable.h"
#include "mymoneyobject.h"
class IMyMoneyProcessingCalendar;
/**
* @author Michael Edwardes
*/
/**
* This class represents a schedule. (A series of bills, deposits or
* transfers).
*
* @short A class to represent a schedule.
* @see MyMoneyScheduled
*/
class KMM_MYMONEY_EXPORT MyMoneySchedule : public MyMoneyObject
{
friend class MyMoneyStorageANON;
Q_GADGET
KMM_MYMONEY_UNIT_TESTABLE
public:
- /**
- * This enum is used to describe all the possible schedule frequencies.
- * The special entry, OCCUR_ANY, is used to combine all the other types.
- */
- enum occurrenceE { OCCUR_ANY = 0, OCCUR_ONCE = 1, OCCUR_DAILY = 2, OCCUR_WEEKLY = 4, OCCUR_FORTNIGHTLY = 8,
- OCCUR_EVERYOTHERWEEK = 16,
- OCCUR_EVERYHALFMONTH = 18,
- OCCUR_EVERYTHREEWEEKS = 20,
- OCCUR_EVERYTHIRTYDAYS = 30,
- OCCUR_MONTHLY = 32, OCCUR_EVERYFOURWEEKS = 64,
- OCCUR_EVERYEIGHTWEEKS = 126,
- OCCUR_EVERYOTHERMONTH = 128, OCCUR_EVERYTHREEMONTHS = 256,
- OCCUR_TWICEYEARLY = 1024, OCCUR_EVERYOTHERYEAR = 2048, OCCUR_QUARTERLY = 4096,
- OCCUR_EVERYFOURMONTHS = 8192, OCCUR_YEARLY = 16384
- };
-
- /**
- * This enum is used to describe the schedule type.
- */
- enum typeE { TYPE_ANY = 0, TYPE_BILL = 1, TYPE_DEPOSIT = 2, TYPE_TRANSFER = 4, TYPE_LOANPAYMENT = 5 };
-
- /**
- * This enum is used to describe the schedule's payment type.
- */
- enum paymentTypeE { STYPE_ANY = 0, STYPE_DIRECTDEBIT = 1, STYPE_DIRECTDEPOSIT = 2,
- STYPE_MANUALDEPOSIT = 4, STYPE_OTHER = 8,
- STYPE_WRITECHEQUE = 16,
- STYPE_STANDINGORDER = 32,
- STYPE_BANKTRANSFER = 64
- };
-
- /**
- * This enum is used by the auto-commit functionality.
- *
- * Depending upon the value of m_weekendOption the schedule can
- * be entered on a different date
- **/
- enum weekendOptionE { MoveBefore = 0, MoveAfter = 1, MoveNothing = 2 };
-
enum elNameE { enPayment, enPayments };
Q_ENUM(elNameE)
enum attrNameE { anName, anType, anOccurence, anOccurenceMultiplier,
anPaymentType, anFixed,
anAutoEnter, anLastPayment, anWeekendOption,
anDate, anStartDate, anEndDate
};
Q_ENUM(attrNameE)
/**
* Standard constructor
*/
explicit MyMoneySchedule();
/**
* Constructor for initialising the object.
*
* Please note that the optional fields are not set and the transaction
* MUST be set before it can be used.
*
* @a startDate is not used anymore and internally set to QDate()
*/
- MyMoneySchedule(const QString& name, typeE type, occurrenceE occurrence, int occurrenceMultiplier,
- paymentTypeE paymentType, const QDate& startDate, const QDate& endDate, bool fixed, bool autoEnter);
+ MyMoneySchedule(const QString& name, eMyMoney::Schedule::Type type, eMyMoney::Schedule::Occurrence occurrence, int occurrenceMultiplier,
+ eMyMoney::Schedule::PaymentType paymentType, const QDate& startDate, const QDate& endDate, bool fixed, bool autoEnter);
explicit MyMoneySchedule(const QDomElement& node);
MyMoneySchedule(const QString& id, const MyMoneySchedule& right);
/**
* Standard destructor
*/
~MyMoneySchedule() {}
/**
* Simple get method that returns the occurrence frequency.
*
- * @return occurrenceE The instance frequency.
+ * @return eMyMoney::Schedule::Occurrence The instance frequency.
*/
- occurrenceE occurrence() const;
+ eMyMoney::Schedule::Occurrence occurrence() const;
/**
* Simple get method that returns the occurrence period
* multiplier and occurrence
*
- * @return occurrenceE The instance period
+ * @return eMyMoney::Schedule::Occurrence The instance period
*
*/
- occurrenceE occurrencePeriod() const {
+ eMyMoney::Schedule::Occurrence occurrencePeriod() const {
return m_occurrence;
}
/**
* Simple get method that returns the occurrence period multiplier.
*
* @return int The frequency multiplier
*/
int occurrenceMultiplier() const {
return m_occurrenceMultiplier;
}
/**
* Simple get method that returns the schedule type.
*
- * @return typeE The instance type.
+ * @return eMyMoney::Schedule::Type The instance type.
*/
- typeE type() const {
+ eMyMoney::Schedule::Type type() const {
return m_type;
}
/**
* Simple get method that returns the schedule startDate. If
* the schedule has been executed once, the date of the first
* execution is returned. Otherwise, the next due date is
* returned.
*
* @return reference to QDate containing the start date.
*/
const QDate& startDate() const;
/**
* Simple get method that returns the schedule paymentType.
*
- * @return paymentTypeE The instance paymentType.
+ * @return eMyMoney::Schedule::PaymentType The instance paymentType.
*/
- paymentTypeE paymentType() const {
+ eMyMoney::Schedule::PaymentType paymentType() const {
return m_paymentType;
}
/**
* Simple get method that returns true if the schedule is fixed.
*
* @return bool To indicate whether the instance is fixed.
*/
bool isFixed() const {
return m_fixed;
}
/**
* Simple get method that returns true if the schedule will end
* at some time.
*
* @return bool Indicates whether the instance will end.
*/
bool willEnd() const {
return m_endDate.isValid();
}
/**
* Simple get method that returns the number of transactions remaining.
*
* @return int The number of transactions remaining for the instance.
*/
int transactionsRemaining() const;
/**
* Simple method that returns the number of transactions remaining
* until a given date.
*
* @param endDate Date to count transactions to.
* @return int The number of transactions remaining for the instance.
*/
int transactionsRemainingUntil(const QDate& endDate) const;
/**
* Simple get method that returns the schedule end date.
*
* @return QDate The end date for the instance.
*/
const QDate& endDate() const {
return m_endDate;
}
/**
* Simple get method that returns true if the transaction should be
* automatically entered into the register.
*
* @return bool Indicates whether the instance will be automatically entered.
*/
bool autoEnter() const {
return m_autoEnter;
}
/**
* Simple get method that returns the transaction data for the schedule.
*
* @return MyMoneyTransaction The transaction data for the instance.
*/
const MyMoneyTransaction& transaction() const {
return m_transaction;
}
/**
* Simple method that returns the schedules last payment. If the
* schedule has never been executed, QDate() will be returned.
*
* @return QDate The last payment for the schedule.
*/
const QDate& lastPayment() const {
return m_lastPayment;
}
/**
* Simple method that returns the next due date for the schedule.
*
* @return reference to QDate containing the next due date.
*
* @note The date returned can represent a value that is past
* a possible end of the schedule. Make sure to consider
* the return value of isFinished() when using the value returned.
*/
const QDate& nextDueDate() const;
/**
* This method returns the next due date adjusted
* according to the rules specified by the schedule's weekend option.
*
* @return QDate containing the adjusted next due date. If the
* schedule is finished (@sa isFinished()) then the method
* returns an invalid QDate.
*
* @sa weekendOption()
* @sa adjustedDate()
*/
QDate adjustedNextDueDate() const;
/**
* This method adjusts returns the date adjusted according to the
* rules specified by the schedule's weekend option.
*
* @return QDate containing the adjusted date.
*/
- QDate adjustedDate(QDate date, weekendOptionE option) const;
+ QDate adjustedDate(QDate date, eMyMoney::Schedule::WeekendOption option) const;
/**
* Get the weekendOption that determines how the schedule check code
* will enter transactions that occur on a non-processing day (usually
* a weekend).
*
* This not used by MyMoneySchedule but by the support code.
**/
- weekendOptionE weekendOption() const {
+ eMyMoney::Schedule::WeekendOption weekendOption() const {
return m_weekendOption;
}
/**
* Simple method that sets the frequency for the schedule.
*
* @param occ The new occurrence (frequency).
* @return none
*/
- void setOccurrence(occurrenceE occ);
+ void setOccurrence(eMyMoney::Schedule::Occurrence occ);
/**
* Simple method that sets the schedule period
*
* @param occ The new occurrence period (frequency)
* @return none
*/
- void setOccurrencePeriod(occurrenceE occ);
+ void setOccurrencePeriod(eMyMoney::Schedule::Occurrence occ);
/**
* Simple method that sets the frequency multiplier for the schedule.
*
* @param occmultiplier The new occurrence (frequency) multiplier.
* @return none
*/
void setOccurrenceMultiplier(int occmultiplier);
/**
* Simple method that sets the type for the schedule.
*
* @param type The new type.
* @return none
*/
- void setType(typeE type);
+ void setType(eMyMoney::Schedule::Type type);
/**
* Simple method that sets the start date for the schedule.
*
* @param date The new start date.
* @return none
*/
void setStartDate(const QDate& date);
/**
* Simple method that sets the payment type for the schedule.
*
* @param type The new payment type.
* @return none
*/
- void setPaymentType(paymentTypeE type);
+ void setPaymentType(eMyMoney::Schedule::PaymentType type);
/**
* Simple method to set whether the schedule is fixed or not.
*
* @param fixed boolean to indicate whether the instance is fixed.
* @return none
*/
void setFixed(bool fixed);
/**
* Simple method that sets the transaction for the schedule.
* The transaction must have a valid postDate set, otherwise
* it will not be accepted.
*
* @param transaction The new transaction.
* @return none
*/
void setTransaction(const MyMoneyTransaction& transaction);
/**
* Simple set method to set the end date for the schedule.
*
* @param date The new end date.
* @return none
*/
void setEndDate(const QDate& date);
/**
* Simple set method to set whether this transaction should be automatically
* entered into the journal whenever it is due.
*
* @param autoenter boolean to indicate whether we need to automatically
* enter the transaction.
* @return none
*/
void setAutoEnter(bool autoenter);
/**
* Simple set method to set the schedule's next payment date.
*
* @param date The next payment date.
* @return none
*/
void setNextDueDate(const QDate& date);
/**
* Simple set method to set the schedule's last payment. If
* this method is called for the first time on the object,
* the @a m_startDate member will be set to @a date as well.
*
* This method should be called whenever a schedule is entered or skipped.
*
* @param date The last payment date.
* @return none
*/
void setLastPayment(const QDate& date);
/**
* Set the weekendOption that determines how the schedule check code
* will enter transactions that occur on a non-processing day (usually
* a weekend). The following values
* are valid:
*
* - MoveNothing: don't modify date
* - MoveBefore: modify the date to the previous processing day
* - MoveAfter: modify the date to the next processing day
*
* If an invalid option is given, the option is set to MoveNothing.
*
* @param option See list in description
* @return none
*
* @note This not used by MyMoneySchedule but by the support code.
**/
- void setWeekendOption(const weekendOptionE option);
+ void setWeekendOption(const eMyMoney::Schedule::WeekendOption option);
/**
* Validates the schedule instance.
*
* Makes sure the paymentType matches the type and that the required
* fields have been set.
*
* @param id_check if @p true, the method will check for an empty id.
* if @p false, this check is skipped. Default is @p true.
*
* @return If this method returns, all checks are passed. Otherwise,
* it will throw a MyMoneyException object.
*
* @exception MyMoneyException with detailed error information is thrown
* in case of failure of any check.
*/
void validate(bool id_check = true) const;
/**
* Calculates the date of the next payment adjusted according to the
* rules specified by the schedule's weekend option.
*
* @param refDate The reference date from which the next payment
* date will be calculated (defaults to current date)
*
* @return QDate The adjusted date the next payment is due. This date is
* always past @a refDate. In case of an error or if there
* are no more payments then an empty/invalid QDate() will
* be returned.
*/
QDate adjustedNextPayment(const QDate& refDate = QDate::currentDate()) const;
/**
* Calculates the date of the next payment.
*
* @param refDate The reference date from which the next payment
* date will be calculated (defaults to current date)
*
* @return QDate The date the next payment is due. This date is
* always past @a refDate. In case of an error or
* if there are no more payments then an empty/invalid QDate()
* will be returned.
*/
QDate nextPayment(const QDate& refDate = QDate::currentDate()) const;
/**
* Calculates the date of the next payment and adjusts if asked.
*
* @param adjust Whether to adjust the calculated date according to the
* rules specified by the schedule's weekend option.
* @param refDate The reference date from which the next payment
* date will be calculated (defaults to current date)
*
* @return QDate The date the next payment is due. This date is
* always past @a refDate. In case of an error or
* if there is no more payments then an empty/invalid QDate()
* will be returned.
*/
QDate nextPaymentDate(const bool& adjust, const QDate& refDate = QDate::currentDate()) const;
/**
* Calculates the dates of the payment over a certain period of time.
*
* An empty list is returned for no payments or error.
*
* @param startDate The start date for the range calculations
* @param endDate The end date for the range calculations.
* @return QList<QDate> The dates on which the payments are due.
*/
QList<QDate> paymentDates(const QDate& startDate, const QDate& endDate) const;
/**
* Returns the instances name
*
* @return The name
*/
const QString& name() const {
return m_name;
}
/**
* Changes the instance name
*
* @param nm The new name
* @return none
*/
void setName(const QString& nm);
bool operator ==(const MyMoneySchedule& right) const;
bool operator !=(const MyMoneySchedule& right) const {
return ! operator==(right);
}
bool operator <(const MyMoneySchedule& right) const;
MyMoneyAccount account(int cnt = 1) const;
MyMoneyAccount transferAccount() const {
return account(2);
};
QDate dateAfter(int transactions) const;
bool isOverdue() const;
bool isFinished() const;
bool hasRecordedPayment(const QDate&) const;
void recordPayment(const QDate&);
QList<QDate> recordedPayments() const {
return m_recordedPayments;
}
void writeXML(QDomDocument& document, QDomElement& parent) const;
/**
* This method checks if a reference to the given object exists. It returns,
* a @p true if the object is referencing the one requested by the
* parameter @p id. If it does not, this method returns @p false.
*
* @param id id of the object to be checked for references
* @retval true This object references object with id @p id.
* @retval false This object does not reference the object with id @p id.
*/
virtual bool hasReferenceTo(const QString& id) const;
/**
* This method replaces all occurrences of id @a oldId with
* @a newId. All other ids are not changed.
*
* @return true if any change has been performed
* @return false if nothing has been modified
*/
bool replaceId(const QString& newId, const QString& oldId);
/**
* Returns the human-readable format of Schedule's occurrence
*
* @return QString representing the human readable format
*/
QString occurrenceToString() const;
/**
* This method is used to convert the occurrence type from its
* internal representation into a human readable format.
*
* @param type numerical representation of the MyMoneySchedule
* occurrence type
*
* @return QString representing the human readable format
*/
- static QString occurrenceToString(occurrenceE type);
+ static QString occurrenceToString(eMyMoney::Schedule::Occurrence type);
/**
* This method is used to convert a multiplier and base occurrence type
* from its internal representation into a human readable format.
* When multiplier * occurrence is equivalent to a simple occurrence
* the method returns the same as occurrenceToString of the simple occurrence
*
* @param mult occurrence multiplier
* @param type occurrence period
*
* @return QString representing the human readable format
*/
- static QString occurrenceToString(int mult, occurrenceE type);
+ static QString occurrenceToString(int mult, eMyMoney::Schedule::Occurrence type);
/**
* This method is used to convert an occurrence period from
* its internal representation into a human-readable format.
*
* @param type numerical representation of the MyMoneySchedule
* occurrence type
*
* @return QString representing the human readable format
*/
- static QString occurrencePeriodToString(occurrenceE type);
+ static QString occurrencePeriodToString(eMyMoney::Schedule::Occurrence type);
/**
* This method is used to convert the payment type from its
* internal representation into a human readable format.
*
* @param paymentType numerical representation of the MyMoneySchedule
* payment type
*
* @return QString representing the human readable format
*/
- static QString paymentMethodToString(MyMoneySchedule::paymentTypeE paymentType);
+ static QString paymentMethodToString(eMyMoney::Schedule::PaymentType paymentType);
/**
* This method is used to convert the schedule weekend option from its
* internal representation into a human readable format.
*
* @param weekendOption numerical representation of the MyMoneySchedule
* weekend option
*
* @return QString representing the human readable format
*/
- static QString weekendOptionToString(MyMoneySchedule::weekendOptionE weekendOption);
+ static QString weekendOptionToString(eMyMoney::Schedule::WeekendOption weekendOption);
/**
* This method is used to convert the schedule type from its
* internal representation into a human readable format.
*
* @param type numerical representation of the MyMoneySchedule
* schedule type
*
* @return QString representing the human readable format
*/
- static QString scheduleTypeToString(MyMoneySchedule::typeE type);
+ static QString scheduleTypeToString(eMyMoney::Schedule::Type type);
int variation() const;
void setVariation(int var);
/**
*
* Convert an occurrence to the maximum number of events possible during a single
* calendar year.
* A fortnight is treated as 15 days.
*
* @param occurrence The occurrence
*
* @return int Number of days between events
*/
- static int eventsPerYear(MyMoneySchedule::occurrenceE occurrence);
+ static int eventsPerYear(eMyMoney::Schedule::Occurrence occurrence);
/**
*
* Convert an occurrence to the number of days between events
* Treats a month as 30 days.
* Treats a fortnight as 15 days.
*
* @param occurrence The occurrence
*
* @return int Number of days between events
*/
- static int daysBetweenEvents(MyMoneySchedule::occurrenceE occurrence);
+ static int daysBetweenEvents(eMyMoney::Schedule::Occurrence occurrence);
/**
* Helper method to convert simple occurrence to compound occurrence + multiplier
*
* @param multiplier Returned by reference. Adjusted multiplier
* @param occurrence Returned by reference. Occurrence type
*/
- static void simpleToCompoundOccurrence(int& multiplier, occurrenceE& occurrence);
+ static void simpleToCompoundOccurrence(int& multiplier, eMyMoney::Schedule::Occurrence& occurrence);
/**
* Helper method to convert compound occurrence + multiplier to simple occurrence
*
* @param multiplier Returned by reference. Adjusted multiplier
* @param occurrence Returned by reference. Occurrence type
*/
- static void compoundToSimpleOccurrence(int& multiplier, occurrenceE& occurrence);
+ static void compoundToSimpleOccurrence(int& multiplier, eMyMoney::Schedule::Occurrence& occurrence);
/**
* This method is used to set the static point to relevant
* IMyMoneyProcessingCalendar.
*/
static void setProcessingCalendar(IMyMoneyProcessingCalendar* pc);
private:
/**
* This method returns a pointer to the processing calendar object.
*
* @return const pointer to the current attached processing calendar object.
* If no object is attached, returns 0.
*/
IMyMoneyProcessingCalendar* processingCalendar() const;
/**
* This method forces the day of the passed @p date to
* be the day of the start date of this schedule kept
* in m_startDate. It is internally used when calculating
* the payment dates over several periods.
*
* @param date reference to QDate object to be checked and adjusted
*/
void fixDate(QDate& date) const;
/**
* Simple method that sets the transaction for the schedule.
* The transaction must have a valid postDate set, otherwise
* it will not be accepted. This test is bypassed, if @a noDateCheck
* is set to true
*
* @param transaction The new transaction.
* @param noDateCheck if @a true, the date check is bypassed
* @return none
*/
void setTransaction(const MyMoneyTransaction& transaction, bool noDateCheck);
/**
* This method adds a number of Half Months to the given Date.
- * This is used for OCCUR_EVERYHALFMONTH occurrences.
+ * This is used for EveryHalfMonth occurrences.
* The addition uses the following rules to add a half month:
* Day 1-13: add 15 days
* Day 14: add 15 days (except February: the last day of the month)
* Day 15: last day of the month
* Day 16-29 (not last day in February): subtract 15 days and add 1 month
* 30 and last day: 15th of next month
*
* This calculation pairs days 1 to 12 with 16 to 27.
* Day 15 is paired with the last day of every month.
* Repeated addition has issues in the following cases:
* - Days 13 to 14 are paired with 28 to 29 until addition hits the last day of February
* after which the (15,last) pair will be used.
* - Addition from Day 30 leads immediately to the (15th,last) day pair.
*
* @param date The date
* @param mult The number of half months to add. Default is 1.
*
* @return QDate date with mult half months added
*/
QDate addHalfMonths(QDate date, int mult = 1) const;
/**
* Checks if a given date should be considered a processing day
* based on a calendar. See @a IMyMoneyProcessingCalendar and
* setProcessingCalendar(). If no processingCalendar has been
* setup using setProcessingCalendar it returns @c true on Mon..Fri
* and @c false on Sat..Sun.
*/
bool isProcessingDate(const QDate& date) const;
static const QString getElName(const elNameE _el);
static const QString getAttrName(const attrNameE _attr);
private:
/// Its occurrence
- occurrenceE m_occurrence;
+ eMyMoney::Schedule::Occurrence m_occurrence;
/// Its occurrence multiplier
int m_occurrenceMultiplier;
/// Its type
- typeE m_type;
+ eMyMoney::Schedule::Type m_type;
/// The date the schedule commences
QDate m_startDate;
/// The payment type
- paymentTypeE m_paymentType;
+ eMyMoney::Schedule::PaymentType m_paymentType;
/// Can the amount vary
bool m_fixed;
/// The, possibly estimated, amount plus all other relevant details
MyMoneyTransaction m_transaction;
/// The last transaction date if the schedule does end at a fixed date
QDate m_endDate;
/// Enter the transaction into the register automatically
bool m_autoEnter;
/// Internal date used for calculations
QDate m_lastPayment;
/// The name
QString m_name;
/// The recorded payments
QList<QDate> m_recordedPayments;
/// The weekend option
- weekendOptionE m_weekendOption;
+ eMyMoney::Schedule::WeekendOption m_weekendOption;
};
/**
* Make it possible to hold @ref MyMoneySchedule objects inside @ref QVariant objects.
*/
Q_DECLARE_METATYPE(MyMoneySchedule)
#endif
diff --git a/kmymoney/mymoney/mymoneytransactionfilter.cpp b/kmymoney/mymoney/mymoneytransactionfilter.cpp
index 4abc2fa3a..1b66c64e7 100644
--- a/kmymoney/mymoney/mymoneytransactionfilter.cpp
+++ b/kmymoney/mymoney/mymoneytransactionfilter.cpp
@@ -1,906 +1,910 @@
/***************************************************************************
mymoneytransactionfilter.cpp - description
-------------------
begin : Fri Aug 22 2003
copyright : (C) 2003 by Thomas Baumgart
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "mymoneytransactionfilter.h"
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneyaccount.h"
+#include "mymoneypayee.h"
+#include "mymoneytag.h"
+#include "mymoneytransaction.h"
MyMoneyTransactionFilter::MyMoneyTransactionFilter()
{
m_filterSet.allFilter = 0;
m_reportAllSplits = true;
m_considerCategory = true;
m_invertText = false;
}
MyMoneyTransactionFilter::MyMoneyTransactionFilter(const QString& id)
{
m_filterSet.allFilter = 0;
m_reportAllSplits = false;
m_considerCategory = false;
m_invertText = false;
addAccount(id);
// addCategory(id);
}
MyMoneyTransactionFilter::~MyMoneyTransactionFilter()
{
}
void MyMoneyTransactionFilter::clear()
{
m_filterSet.allFilter = 0;
m_invertText = false;
m_accounts.clear();
m_categories.clear();
m_payees.clear();
m_tags.clear();
m_types.clear();
m_states.clear();
m_validity.clear();
m_matchingSplits.clear();
m_fromDate = QDate();
m_toDate = QDate();
}
void MyMoneyTransactionFilter::clearAccountFilter()
{
m_filterSet.singleFilter.accountFilter = 0;
m_accounts.clear();
}
void MyMoneyTransactionFilter::setTextFilter(const QRegExp& text, bool invert)
{
m_filterSet.singleFilter.textFilter = 1;
m_invertText = invert;
m_text = text;
}
void MyMoneyTransactionFilter::addAccount(const QStringList& ids)
{
QStringList::ConstIterator it;
m_filterSet.singleFilter.accountFilter = 1;
for (it = ids.begin(); it != ids.end(); ++it)
addAccount(*it);
}
void MyMoneyTransactionFilter::addAccount(const QString& id)
{
if (!m_accounts.isEmpty() && !id.isEmpty()) {
if (m_accounts.find(id) != m_accounts.end())
return;
}
m_filterSet.singleFilter.accountFilter = 1;
if (!id.isEmpty())
m_accounts.insert(id, "");
}
void MyMoneyTransactionFilter::addCategory(const QStringList& ids)
{
QStringList::ConstIterator it;
m_filterSet.singleFilter.categoryFilter = 1;
for (it = ids.begin(); it != ids.end(); ++it)
addCategory(*it);
}
void MyMoneyTransactionFilter::addCategory(const QString& id)
{
if (!m_categories.isEmpty() && !id.isEmpty()) {
if (m_categories.end() != m_categories.find(id))
return;
}
m_filterSet.singleFilter.categoryFilter = 1;
if (!id.isEmpty())
m_categories.insert(id, "");
}
void MyMoneyTransactionFilter::setDateFilter(const QDate& from, const QDate& to)
{
m_filterSet.singleFilter.dateFilter = from.isValid() | to.isValid();
m_fromDate = from;
m_toDate = to;
}
void MyMoneyTransactionFilter::setAmountFilter(const MyMoneyMoney& from, const MyMoneyMoney& to)
{
m_filterSet.singleFilter.amountFilter = 1;
m_fromAmount = from.abs();
m_toAmount = to.abs();
// make sure that the user does not try to fool us ;-)
if (from > to) {
MyMoneyMoney tmp = m_fromAmount;
m_fromAmount = m_toAmount;
m_toAmount = tmp;
}
}
void MyMoneyTransactionFilter::addPayee(const QString& id)
{
if (!m_payees.isEmpty() && !id.isEmpty()) {
if (m_payees.find(id) != m_payees.end())
return;
}
m_filterSet.singleFilter.payeeFilter = 1;
if (!id.isEmpty())
m_payees.insert(id, "");
}
void MyMoneyTransactionFilter::addTag(const QString& id)
{
if (!m_tags.isEmpty() && !id.isEmpty()) {
if (m_tags.find(id) != m_tags.end())
return;
}
m_filterSet.singleFilter.tagFilter = 1;
if (!id.isEmpty())
m_tags.insert(id, "");
}
void MyMoneyTransactionFilter::addType(const int type)
{
if (!m_types.isEmpty()) {
if (m_types.find(type) != m_types.end())
return;
}
m_filterSet.singleFilter.typeFilter = 1;
m_types.insert(type, "");
}
void MyMoneyTransactionFilter::addState(const int state)
{
if (!m_states.isEmpty()) {
if (m_states.find(state) != m_states.end())
return;
}
m_filterSet.singleFilter.stateFilter = 1;
m_states.insert(state, "");
}
void MyMoneyTransactionFilter::addValidity(const int type)
{
if (!m_validity.isEmpty()) {
if (m_validity.find(type) != m_validity.end())
return;
}
m_filterSet.singleFilter.validityFilter = 1;
m_validity.insert(type, "");
}
void MyMoneyTransactionFilter::setNumberFilter(const QString& from, const QString& to)
{
m_filterSet.singleFilter.nrFilter = 1;
m_fromNr = from;
m_toNr = to;
}
void MyMoneyTransactionFilter::setReportAllSplits(const bool report)
{
m_reportAllSplits = report;
}
void MyMoneyTransactionFilter::setConsiderCategory(const bool check)
{
m_considerCategory = check;
}
const QList<MyMoneySplit>& MyMoneyTransactionFilter::matchingSplits() const
{
return m_matchingSplits;
}
bool MyMoneyTransactionFilter::matchText(const MyMoneySplit * const sp) const
{
// check if the text is contained in one of the fields
// memo, value, number, payee, tag, account, date
if (m_filterSet.singleFilter.textFilter) {
MyMoneyFile* file = MyMoneyFile::instance();
const MyMoneyAccount& acc = file->account(sp->accountId());
const MyMoneySecurity& sec = file->security(acc.currencyId());
if (sp->memo().contains(m_text)
|| sp->shares().formatMoney(acc.fraction(sec)).contains(m_text)
|| sp->value().formatMoney(acc.fraction(sec)).contains(m_text)
|| sp->number().contains(m_text)
|| (m_text.pattern() == sp->transactionId()))
return !m_invertText;
if (acc.name().contains(m_text))
return !m_invertText;
if (!sp->payeeId().isEmpty()) {
const MyMoneyPayee& payee = file->payee(sp->payeeId());
if (payee.name().contains(m_text))
return !m_invertText;
}
if (!sp->tagIdList().isEmpty()) {
QList<QString>::ConstIterator it_s;
QList<QString> t = sp->tagIdList();
for (it_s = t.constBegin(); it_s != t.constEnd(); ++it_s) {
const MyMoneyTag& tag = file->tag((*it_s));
if (tag.name().contains(m_text))
return !m_invertText;
}
}
return m_invertText;
}
return true;
}
bool MyMoneyTransactionFilter::matchAmount(const MyMoneySplit * const sp) const
{
if (m_filterSet.singleFilter.amountFilter) {
if (((sp->value().abs() < m_fromAmount) || sp->value().abs() > m_toAmount)
&& ((sp->shares().abs() < m_fromAmount) || sp->shares().abs() > m_toAmount))
return false;
}
return true;
}
bool MyMoneyTransactionFilter::match(const MyMoneySplit * const sp) const
{
return matchText(sp) && matchAmount(sp);
}
bool MyMoneyTransactionFilter::match(const MyMoneyTransaction& transaction)
{
MyMoneyFile* file = MyMoneyFile::instance();
m_matchingSplits.clear();
// qDebug("T: %s", transaction.id().data());
// if no filter is set, we can safely return a match
// if we should report all splits, then we collect them
if (!m_filterSet.allFilter) {
if (m_reportAllSplits) {
m_matchingSplits = transaction.splits();
}
return true;
}
// perform checks on the MyMoneyTransaction object first
// check the date range
if (m_filterSet.singleFilter.dateFilter) {
if (m_fromDate != QDate()) {
if (transaction.postDate() < m_fromDate)
return false;
}
if (m_toDate != QDate()) {
if (transaction.postDate() > m_toDate)
return false;
}
}
// construct a local list of pointers to all splits and
// remove the ones that do not match account and/or categories.
QList<const MyMoneySplit*> matchingSplits;
//QList<MyMoneySplit> matchingSplits;
foreach (const MyMoneySplit& s, transaction.splits()) {
matchingSplits.append(&s);
}
bool categoryMatched = !m_filterSet.singleFilter.categoryFilter;
bool accountMatched = !m_filterSet.singleFilter.accountFilter;
bool isTransfer = true;
// check the transaction's validity
if (m_filterSet.singleFilter.validityFilter) {
if (m_validity.count() > 0) {
if (m_validity.end() == m_validity.find(validTransaction(transaction)))
return false;
}
}
QMutableListIterator<const MyMoneySplit*> sp(matchingSplits);
if (m_filterSet.singleFilter.accountFilter == 1
|| m_filterSet.singleFilter.categoryFilter == 1) {
for (sp.toFront(); sp.hasNext();) {
bool removeSplit = true;
const MyMoneySplit* & s = sp.next();
const MyMoneyAccount& acc = file->account(s->accountId());
if (m_considerCategory) {
switch (acc.accountGroup()) {
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
+ case eMyMoney::Account::Income:
+ case eMyMoney::Account::Expense:
isTransfer = false;
// check if the split references one of the categories in the list
if (m_filterSet.singleFilter.categoryFilter) {
if (m_categories.count() > 0) {
if (m_categories.end() != m_categories.find(s->accountId())) {
categoryMatched = true;
removeSplit = false;
}
} else {
// we're looking for transactions with 'no' categories
return false;
}
}
break;
default:
// check if the split references one of the accounts in the list
if (m_filterSet.singleFilter.accountFilter) {
if (m_accounts.count() > 0) {
if (m_accounts.end() != m_accounts.find(s->accountId())) {
accountMatched = true;
removeSplit = false;
}
}
} else
removeSplit = false;
break;
}
} else {
if (m_filterSet.singleFilter.accountFilter) {
if (m_accounts.count() > 0) {
if (m_accounts.end() != m_accounts.find(s->accountId())) {
accountMatched = true;
removeSplit = false;
}
}
} else
removeSplit = false;
}
if (removeSplit) {
// qDebug(" S: %s", (*it).id().data());
sp.remove();
}
}
}
// check if we're looking for transactions without assigned category
if (!categoryMatched && transaction.splitCount() == 1 && m_categories.count() == 0) {
categoryMatched = true;
}
// if there's no category filter and the category did not
// match, then we still want to see this transaction if it's
// a transfer
if (!categoryMatched && !m_filterSet.singleFilter.categoryFilter)
categoryMatched = isTransfer;
if (matchingSplits.count() == 0
|| !(accountMatched && categoryMatched))
return false;
FilterSet filterSet = m_filterSet;
filterSet.singleFilter.dateFilter =
filterSet.singleFilter.accountFilter =
filterSet.singleFilter.categoryFilter = 0;
// check if we still have something to do
if (filterSet.allFilter != 0) {
for (sp.toFront(); sp.hasNext();) {
bool removeSplit = true;
const MyMoneySplit* & s = sp.next();
removeSplit = !(matchAmount(s) && matchText(s));
const MyMoneyAccount& acc = file->account(s->accountId());
// Determine if this account is a category or an account
bool isCategory = false;
switch (acc.accountGroup()) {
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
+ case eMyMoney::Account::Income:
+ case eMyMoney::Account::Expense:
isCategory = true;
default:
break;
}
if (!isCategory && !removeSplit) {
// check the payee list
if (!removeSplit && m_filterSet.singleFilter.payeeFilter) {
if (m_payees.count() > 0) {
if (s->payeeId().isEmpty() || m_payees.end() == m_payees.find(s->payeeId()))
removeSplit = true;
} else if (!s->payeeId().isEmpty())
removeSplit = true;
}
// check the tag list
if (!removeSplit && m_filterSet.singleFilter.tagFilter) {
if (m_tags.count() > 0) {
if (s->tagIdList().isEmpty())
removeSplit = true;
else {
bool found = false;
for (int i = 0; i < s->tagIdList().size(); i++) {
if (m_tags.end() != m_tags.find(s->tagIdList()[i])) {
found = true;
break;
}
}
if (!found) {
removeSplit = true;
}
}
} else if (!s->tagIdList().isEmpty())
removeSplit = true;
}
// check the type list
if (!removeSplit && m_filterSet.singleFilter.typeFilter) {
if (m_types.count() > 0) {
if (m_types.end() == m_types.find(splitType(transaction, *s)))
removeSplit = true;
}
}
// check the state list
if (!removeSplit && m_filterSet.singleFilter.stateFilter) {
if (m_states.count() > 0) {
if (m_states.end() == m_states.find(splitState(*s)))
removeSplit = true;
}
}
if (!removeSplit && m_filterSet.singleFilter.nrFilter) {
if (!m_fromNr.isEmpty()) {
if (s->number() < m_fromNr)
removeSplit = true;
}
if (!m_toNr.isEmpty()) {
if (s->number() > m_toNr)
removeSplit = true;
}
}
} else if (m_filterSet.singleFilter.payeeFilter
|| m_filterSet.singleFilter.tagFilter
|| m_filterSet.singleFilter.typeFilter
|| m_filterSet.singleFilter.stateFilter
|| m_filterSet.singleFilter.nrFilter)
removeSplit = true;
if (removeSplit) {
// qDebug(" S: %s", (*it).id().data());
sp.remove();
}
}
}
if (m_reportAllSplits == false && matchingSplits.count() != 0) {
m_matchingSplits.append(transaction.splits()[0]);
} else {
foreach (const MyMoneySplit* s, matchingSplits) {
m_matchingSplits.append(*s);
}
}
// all filters passed, I guess we have a match
// qDebug(" C: %d", m_matchingSplits.count());
return matchingSplits.count() != 0;
}
int MyMoneyTransactionFilter::splitState(const MyMoneySplit& split) const
{
- int rc = notReconciled;
+ int rc = (int)eMyMoney::TransactionFilter::State::NotReconciled;
switch (split.reconcileFlag()) {
default:
case MyMoneySplit::NotReconciled:
break;;
case MyMoneySplit::Cleared:
- rc = cleared;
+ rc = (int)eMyMoney::TransactionFilter::State::Cleared;
break;
case MyMoneySplit::Reconciled:
- rc = reconciled;
+ rc = (int)eMyMoney::TransactionFilter::State::Reconciled;
break;
case MyMoneySplit::Frozen:
- rc = frozen;
+ rc = (int)eMyMoney::TransactionFilter::State::Frozen;
break;
}
return rc;
}
int MyMoneyTransactionFilter::splitType(const MyMoneyTransaction& t, const MyMoneySplit& split) const
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount a, b;
a = file->account(split.accountId());
- if ((a.accountGroup() == MyMoneyAccount::Income
- || a.accountGroup() == MyMoneyAccount::Expense))
+ if ((a.accountGroup() == eMyMoney::Account::Income
+ || a.accountGroup() == eMyMoney::Account::Expense))
return allTypes;
if (t.splitCount() == 2) {
QString ida, idb;
if (t.splits().size() > 0)
ida = t.splits()[0].accountId();
if (t.splits().size() > 1)
idb = t.splits()[1].accountId();
a = file->account(ida);
b = file->account(idb);
- if ((a.accountGroup() != MyMoneyAccount::Expense
- && a.accountGroup() != MyMoneyAccount::Income)
- && (b.accountGroup() != MyMoneyAccount::Expense
- && b.accountGroup() != MyMoneyAccount::Income))
+ if ((a.accountGroup() != eMyMoney::Account::Expense
+ && a.accountGroup() != eMyMoney::Account::Income)
+ && (b.accountGroup() != eMyMoney::Account::Expense
+ && b.accountGroup() != eMyMoney::Account::Income))
return transfers;
}
if (split.value().isPositive())
return deposits;
return payments;
}
MyMoneyTransactionFilter::validityOptionE MyMoneyTransactionFilter::validTransaction(const MyMoneyTransaction& t) const
{
QList<MyMoneySplit>::ConstIterator it_s;
MyMoneyMoney val;
for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
val += (*it_s).value();
}
return (val == MyMoneyMoney()) ? valid : invalid;
}
bool MyMoneyTransactionFilter::includesCategory(const QString& cat) const
{
return (! m_filterSet.singleFilter.categoryFilter) || m_categories.end() != m_categories.find(cat);
}
bool MyMoneyTransactionFilter::includesAccount(const QString& acc) const
{
return (! m_filterSet.singleFilter.accountFilter) || m_accounts.end() != m_accounts.find(acc);
}
bool MyMoneyTransactionFilter::includesPayee(const QString& pye) const
{
return (! m_filterSet.singleFilter.payeeFilter) || m_payees.end() != m_payees.find(pye);
}
bool MyMoneyTransactionFilter::includesTag(const QString& tag) const
{
return (! m_filterSet.singleFilter.tagFilter) || m_tags.end() != m_tags.find(tag);
}
bool MyMoneyTransactionFilter::dateFilter(QDate& from, QDate& to) const
{
from = m_fromDate;
to = m_toDate;
return m_filterSet.singleFilter.dateFilter == 1;
}
bool MyMoneyTransactionFilter::amountFilter(MyMoneyMoney& from, MyMoneyMoney& to) const
{
from = m_fromAmount;
to = m_toAmount;
return m_filterSet.singleFilter.amountFilter == 1;
}
bool MyMoneyTransactionFilter::numberFilter(QString& from, QString& to) const
{
from = m_fromNr;
to = m_toNr;
return m_filterSet.singleFilter.nrFilter == 1;
}
bool MyMoneyTransactionFilter::payees(QStringList& list) const
{
bool result = m_filterSet.singleFilter.payeeFilter;
if (result) {
QHashIterator<QString, QString> it_payee(m_payees);
while (it_payee.hasNext()) {
it_payee.next();
list += it_payee.key();
}
}
return result;
}
bool MyMoneyTransactionFilter::tags(QStringList& list) const
{
bool result = m_filterSet.singleFilter.tagFilter;
if (result) {
QHashIterator<QString, QString> it_tag(m_tags);
while (it_tag.hasNext()) {
it_tag.next();
list += it_tag.key();
}
}
return result;
}
bool MyMoneyTransactionFilter::accounts(QStringList& list) const
{
bool result = m_filterSet.singleFilter.accountFilter;
if (result) {
QHashIterator<QString, QString> it_account(m_accounts);
while (it_account.hasNext()) {
it_account.next();
QString account = it_account.key();
list += account;
}
}
return result;
}
bool MyMoneyTransactionFilter::categories(QStringList& list) const
{
bool result = m_filterSet.singleFilter.categoryFilter;
if (result) {
QHashIterator<QString, QString> it_category(m_categories);
while (it_category.hasNext()) {
it_category.next();
list += it_category.key();
}
}
return result;
}
bool MyMoneyTransactionFilter::types(QList<int>& list) const
{
bool result = m_filterSet.singleFilter.typeFilter;
if (result) {
QHashIterator<int, QString> it_type(m_types);
while (it_type.hasNext()) {
it_type.next();
list += it_type.key();
}
}
return result;
}
bool MyMoneyTransactionFilter::states(QList<int>& list) const
{
bool result = m_filterSet.singleFilter.stateFilter;
if (result) {
QHashIterator<int, QString> it_state(m_states);
while (it_state.hasNext()) {
it_state.next();
list += it_state.key();
}
}
return result;
}
bool MyMoneyTransactionFilter::firstType(int&i) const
{
bool result = m_filterSet.singleFilter.typeFilter;
if (result) {
QHashIterator<int, QString> it_type(m_types);
if (it_type.hasNext()) {
it_type.next();
i = it_type.key();
}
}
return result;
}
bool MyMoneyTransactionFilter::firstState(int&i) const
{
bool result = m_filterSet.singleFilter.stateFilter;
if (result) {
QHashIterator<int, QString> it_state(m_states);
if (it_state.hasNext()) {
it_state.next();
i = it_state.key();
}
}
return result;
}
bool MyMoneyTransactionFilter::textFilter(QRegExp& exp) const
{
exp = m_text;
return m_filterSet.singleFilter.textFilter == 1;
}
void MyMoneyTransactionFilter::setDateFilter(dateOptionE range)
{
QDate from, to;
if (translateDateRange(range, from, to))
setDateFilter(from, to);
}
static int fiscalYearStartMonth = 1;
static int fiscalYearStartDay = 1;
void MyMoneyTransactionFilter::setFiscalYearStart(int firstMonth, int firstDay)
{
fiscalYearStartMonth = firstMonth;
fiscalYearStartDay = firstDay;
}
bool MyMoneyTransactionFilter::translateDateRange(dateOptionE id, QDate& start, QDate& end)
{
bool rc = true;
int yr = QDate::currentDate().year();
int mon = QDate::currentDate().month();
switch (id) {
case MyMoneyTransactionFilter::allDates:
start = QDate();
end = QDate();
break;
case MyMoneyTransactionFilter::asOfToday:
start = QDate();
end = QDate::currentDate();
break;
case MyMoneyTransactionFilter::currentMonth:
start = QDate(yr, mon, 1);
end = QDate(yr, mon, 1).addMonths(1).addDays(-1);
break;
case MyMoneyTransactionFilter::currentYear:
start = QDate(yr, 1, 1);
end = QDate(yr, 12, 31);
break;
case MyMoneyTransactionFilter::monthToDate:
start = QDate(yr, mon, 1);
end = QDate::currentDate();
break;
case MyMoneyTransactionFilter::yearToDate:
start = QDate(yr, 1, 1);
end = QDate::currentDate();
break;
case MyMoneyTransactionFilter::yearToMonth:
start = QDate(yr, 1, 1);
end = QDate(yr, mon, 1).addDays(-1);
break;
case MyMoneyTransactionFilter::lastMonth:
start = QDate(yr, mon, 1).addMonths(-1);
end = QDate(yr, mon, 1).addDays(-1);
break;
case MyMoneyTransactionFilter::lastYear:
start = QDate(yr, 1, 1).addYears(-1);
end = QDate(yr, 12, 31).addYears(-1);
break;
case MyMoneyTransactionFilter::last7Days:
start = QDate::currentDate().addDays(-7);
end = QDate::currentDate();
break;
case MyMoneyTransactionFilter::last30Days:
start = QDate::currentDate().addDays(-30);
end = QDate::currentDate();
break;
case MyMoneyTransactionFilter::last3Months:
start = QDate::currentDate().addMonths(-3);
end = QDate::currentDate();
break;
case MyMoneyTransactionFilter::last6Months:
start = QDate::currentDate().addMonths(-6);
end = QDate::currentDate();
break;
case MyMoneyTransactionFilter::last11Months:
start = QDate(yr, mon, 1).addMonths(-12);
end = QDate(yr, mon, 1).addDays(-1);
break;
case MyMoneyTransactionFilter::last12Months:
start = QDate::currentDate().addMonths(-12);
end = QDate::currentDate();
break;
case MyMoneyTransactionFilter::next7Days:
start = QDate::currentDate();
end = QDate::currentDate().addDays(7);
break;
case MyMoneyTransactionFilter::next30Days:
start = QDate::currentDate();
end = QDate::currentDate().addDays(30);
break;
case MyMoneyTransactionFilter::next3Months:
start = QDate::currentDate();
end = QDate::currentDate().addMonths(3);
break;
case MyMoneyTransactionFilter::next6Months:
start = QDate::currentDate();
end = QDate::currentDate().addMonths(6);
break;
case MyMoneyTransactionFilter::next12Months:
start = QDate::currentDate();
end = QDate::currentDate().addMonths(12);
break;
case MyMoneyTransactionFilter::next18Months:
start = QDate::currentDate();
end = QDate::currentDate().addMonths(18);
break;
case MyMoneyTransactionFilter::userDefined:
start = QDate();
end = QDate();
break;
case MyMoneyTransactionFilter::last3ToNext3Months:
start = QDate::currentDate().addMonths(-3);
end = QDate::currentDate().addMonths(3);
break;
case MyMoneyTransactionFilter::currentQuarter:
start = QDate(yr, mon - ((mon - 1) % 3), 1);
end = start.addMonths(3).addDays(-1);
break;
case MyMoneyTransactionFilter::lastQuarter:
start = QDate(yr, mon - ((mon - 1) % 3), 1).addMonths(-3);
end = start.addMonths(3).addDays(-1);
break;
case MyMoneyTransactionFilter::nextQuarter:
start = QDate(yr, mon - ((mon - 1) % 3), 1).addMonths(3);
end = start.addMonths(3).addDays(-1);
break;
case MyMoneyTransactionFilter::currentFiscalYear:
start = QDate(QDate::currentDate().year(), fiscalYearStartMonth, fiscalYearStartDay);
if (QDate::currentDate() < start)
start = start.addYears(-1);
end = start.addYears(1).addDays(-1);
break;
case MyMoneyTransactionFilter::lastFiscalYear:
start = QDate(QDate::currentDate().year(), fiscalYearStartMonth, fiscalYearStartDay);
if (QDate::currentDate() < start)
start = start.addYears(-1);
start = start.addYears(-1);
end = start.addYears(1).addDays(-1);
break;
case MyMoneyTransactionFilter::today:
start = QDate::currentDate();
end = QDate::currentDate();
break;
default:
qWarning("Unknown date identifier %d in MyMoneyTransactionFilter::translateDateRange()", id);
rc = false;
break;
}
return rc;
}
void MyMoneyTransactionFilter::removeReference(const QString& id)
{
if (m_accounts.end() != m_accounts.find(id)) {
qDebug("%s", qPrintable(QString("Remove account '%1' from report").arg(id)));
m_accounts.take(id);
} else if (m_categories.end() != m_categories.find(id)) {
qDebug("%s", qPrintable(QString("Remove category '%1' from report").arg(id)));
m_categories.remove(id);
} else if (m_payees.end() != m_payees.find(id)) {
qDebug("%s", qPrintable(QString("Remove payee '%1' from report").arg(id)));
m_payees.remove(id);
} else if (m_tags.end() != m_tags.find(id)) {
qDebug("%s", qPrintable(QString("Remove tag '%1' from report").arg(id)));
m_tags.remove(id);
}
}
diff --git a/kmymoney/mymoney/mymoneytransactionfilter.h b/kmymoney/mymoney/mymoneytransactionfilter.h
index 49cc71a84..d6f33edc8 100644
--- a/kmymoney/mymoney/mymoneytransactionfilter.h
+++ b/kmymoney/mymoney/mymoneytransactionfilter.h
@@ -1,620 +1,608 @@
/***************************************************************************
mymoneytransactionfilter.h - description
-------------------
begin : Fri Aug 22 2003
copyright : (C) 2000-2003 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 MYMONEYTRANSACTIONFILTER_H
#define MYMONEYTRANSACTIONFILTER_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QMetaType>
#include <QString>
#include <QDate>
#include <QList>
#include <QHash>
#include <QRegExp>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "kmm_mymoney_export.h"
#include "mymoneymoney.h"
#include "mymoneysplit.h"
/**
* @author Thomas Baumgart
*/
class MyMoneyTransaction;
class KMM_MYMONEY_EXPORT MyMoneyTransactionFilter
{
public:
// Make sure to keep the following enum valus in sync with the values
// used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
enum typeOptionE {
allTypes = 0,
payments,
deposits,
transfers,
// insert new constants above of this line
typeOptionCount
};
- // Make sure to keep the following enum valus in sync with the values
- // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
- enum stateOptionE {
- allStates = 0,
- notReconciled,
- cleared,
- reconciled,
- frozen,
- // insert new constants above of this line
- stateOptionCount
- };
-
// Make sure to keep the following enum valus in sync with the values
// used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
enum validityOptionE {
anyValidity = 0,
valid,
invalid,
// insert new constants above of this line
validityOptionCount
};
// Make sure to keep the following enum valus in sync with the values
// used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui)
enum dateOptionE {
allDates = 0,
asOfToday,
currentMonth,
currentYear,
monthToDate,
yearToDate,
yearToMonth,
lastMonth,
lastYear,
last7Days,
last30Days,
last3Months,
last6Months,
last12Months,
next7Days,
next30Days,
next3Months,
next6Months,
next12Months,
userDefined,
last3ToNext3Months,
last11Months,
currentQuarter,
lastQuarter,
nextQuarter,
currentFiscalYear,
lastFiscalYear,
today,
next18Months,
// insert new constants above of this line
dateOptionCount
};
typedef union {
unsigned allFilter;
struct {
unsigned textFilter : 1;
unsigned accountFilter : 1;
unsigned payeeFilter : 1;
unsigned tagFilter : 1;
unsigned categoryFilter : 1;
unsigned nrFilter : 1;
unsigned dateFilter : 1;
unsigned amountFilter : 1;
unsigned typeFilter : 1;
unsigned stateFilter : 1;
unsigned validityFilter : 1;
} singleFilter;
} FilterSet;
/**
* This is the standard constructor for a transaction filter.
* It creates the object and calls setReportAllSplits() to
* report all matching splits as separate entries. Use
* setReportAllSplits() to override this behaviour.
*/
MyMoneyTransactionFilter();
/**
* This is a convenience constructor to allow construction of
* a simple account filter. It is basically the same as the
* following:
*
* @code
* :
* MyMoneyTransactionFilter filter;
* filter.setReportAllSplits(false);
* filter.addAccount(id);
* :
* @endcode
*
* @param id reference to account id
*/
MyMoneyTransactionFilter(const QString& id);
~MyMoneyTransactionFilter();
/**
* This method is used to clear the filter. All settings will be
* removed.
*/
void clear();
/**
* This method is used to clear the accounts filter only.
*/
void clearAccountFilter();
/**
* This method is used to set the regular expression filter to the value specified
* as parameter @p exp. The following text based fields are searched:
*
* - Memo
* - Payee
* - Tag
* - Category
* - Shares / Value
* - Number
*
* @param exp The regular expression that must be found in a transaction
* before it is included in the result set.
* @param invert If true, value must not be contained in any of the above mentioned fields
*
*/
void setTextFilter(const QRegExp& exp, bool invert = false);
/**
* This method will add the account with id @p id to the list of matching accounts.
* If the list is empty, any transaction will match.
*
* @param id internal ID of the account
*/
void addAccount(const QString& id);
/**
* This is a convenience method and behaves exactly like the above
* method but for a list of id's.
*/
void addAccount(const QStringList& ids);
/**
* This method will add the category with id @p id to the list of matching categories.
* If the list is empty, only transaction with a single asset/liability account will match.
*
* @param id internal ID of the account
*/
void addCategory(const QString& id);
/**
* This is a convenience method and behaves exactly like the above
* method but for a list of id's.
*/
void addCategory(const QStringList& ids);
/**
* This method sets the date filter to match only transactions with posting dates in
* the date range specified by @p from and @p to. If @p from equal QDate()
* all transactions with dates prior to @p to match. If @p to equals QDate()
* all transactions with posting dates past @p from match. If @p from and @p to
* are equal QDate() the filter is not activated and all transactions match.
*
* @param from from date
* @param to to date
*/
void setDateFilter(const QDate& from, const QDate& to);
void setDateFilter(dateOptionE range);
/**
* This method sets the amount filter to match only transactions with
* an amount in the range specified by @p from and @p to.
* If a specific amount should be searched, @p from and @p to should be
* the same value.
*
* @param from smallest value to match
* @param to largest value to match
*/
void setAmountFilter(const MyMoneyMoney& from, const MyMoneyMoney& to);
/**
* This method will add the payee with id @p id to the list of matching payees.
* If the list is empty, any transaction will match.
*
* @param id internal id of the payee
*/
void addPayee(const QString& id);
/**
* This method will add the tag with id @ta id to the list of matching tags.
* If the list is empty, any transaction will match.
*
* @param id internal id of the tag
*/
void addTag(const QString& id);
/**
*/
void addType(const int type);
/**
*/
void addValidity(const int type);
/**
*/
void addState(const int state);
/**
* This method sets the number filter to match only transactions with
* a number in the range specified by @p from and @p to.
* If a specific number should be searched, @p from and @p to should be
* the same value.
*
* @param from smallest value to match
* @param to largest value to match
*
* @note @p from and @p to can contain alphanumeric text
*/
void setNumberFilter(const QString& from, const QString& to);
/**
* This method is used to check a specific transaction against the filter.
* The transaction will match the whole filter, if all specified filters
* match. If the filter is cleared using the clear() method, any transaciton
* matches.
*
* @param transaction A transaction
*
* @retval true The transaction matches the filter set
* @retval false The transaction does not match at least one of
* the filters in the filter set
*/
bool match(const MyMoneyTransaction& transaction);
/**
* This method is used to check a specific split against the
* text filter. The split will match if all specified and
* checked filters match. If the filter is cleared using the clear()
* method, any split matches.
*
* @param sp pointer to the split to be checked
*
* @retval true The split matches the filter set
* @retval false The split does not match at least one of
* the filters in the filter set
*/
bool matchText(const MyMoneySplit * const sp) const;
/**
* This method is used to check a specific split against the
* amount filter. The split will match if all specified and
* checked filters match. If the filter is cleared using the clear()
* method, any split matches.
*
* @param sp pointer to the split to be checked
*
* @retval true The split matches the filter set
* @retval false The split does not match at least one of
* the filters in the filter set
*/
bool matchAmount(const MyMoneySplit * const sp) const;
/**
* Convenience method which actually returns matchText(sp) && matchAmount(sp).
*/
bool match(const MyMoneySplit * const sp) const;
/**
* This method is used to switch the amount of splits reported
* by matchingSplits(). If the argument @p report is @p true (the default
* if no argument specified) then matchingSplits() will return all
* matching splits of the transaction. If @p report is set to @p false,
* then only the very first matching split will be returned by
* matchingSplits().
*
* @param report controls the behaviour of matchingsSplits() as explained above.
*/
void setReportAllSplits(const bool report = true);
void setConsiderCategory(const bool check = true);
/**
* This method returns a list of the matching splits for the filter.
* If m_reportAllSplits is set to false, then only the very first
* split will be returned. Use setReportAllSplits() to change the
* behaviour.
*
* @return reference list of MyMoneySplit objects containing the
* matching splits. If multiple splits match, only the first
* one will be returned.
*
* @note an empty list will be returned, if the filter only required
* to check the data contained in the MyMoneyTransaction
* object (e.g. posting-date, state, etc.).
*
* @note The constructors set m_reportAllSplits differently. Please
* see the documentation of the constructors MyMoneyTransactionFilter()
* and MyMoneyTransactionFilter(const QString&) for details.
*/
const QList<MyMoneySplit>& matchingSplits() const;
/**
* This method returns the from date set in the filter. If
* no value has been set up for this filter, then QDate() is
* returned.
*
* @return returns m_fromDate
*/
const QDate fromDate() const {
return m_fromDate;
};
/**
* This method returns the to date set in the filter. If
* no value has been set up for this filter, then QDate() is
* returned.
*
* @return returns m_toDate
*/
const QDate toDate() const {
return m_toDate;
};
/**
* This method is used to return information about the
* presence of a specific category in the category filter.
* The category in question is included in the filter set,
* if it has been set or no category filter is set.
*
* @param cat id of category in question
* @return true if category is in filter set, false otherwise
*/
bool includesCategory(const QString& cat) const;
/**
* This method is used to return information about the
* presence of a specific account in the account filter.
* The account in question is included in the filter set,
* if it has been set or no account filter is set.
*
* @param acc id of account in question
* @return true if account is in filter set, false otherwise
*/
bool includesAccount(const QString& acc) const;
/**
* This method is used to return information about the
* presence of a specific payee in the account filter.
* The payee in question is included in the filter set,
* if it has been set or no account filter is set.
*
* @param pye id of payee in question
* @return true if payee is in filter set, false otherwise
*/
bool includesPayee(const QString& pye) const;
/**
* This method is used to return information about the
* presence of a specific tag in the account filter.
* The tag in question is included in the filter set,
* if it has been set or no account filter is set.
*
* @param tag id of tag in question
* @return true if tag is in filter set, false otherwise
*/
bool includesTag(const QString& tag) const;
/**
* This method is used to return information about the
* presence of a date filter.
*
* @param from result value for the beginning of the date range
* @param to result value for the end of the date range
* @return true if a date filter is set
*/
bool dateFilter(QDate& from, QDate& to) const;
/**
* This method is used to return information about the
* presence of an amount filter.
*
* @param from result value for the low end of the amount range
* @param to result value for the high end of the amount range
* @return true if an amount filter is set
*/
bool amountFilter(MyMoneyMoney& from, MyMoneyMoney& to) const;
/**
* This method is used to return information about the
* presence of an number filter.
*
* @param from result value for the low end of the number range
* @param to result value for the high end of the number range
* @return true if a number filter is set
*/
bool numberFilter(QString& from, QString& to) const;
/**
* This method returns whether a payee filter has been set,
* and if so, it returns all the payees set in the filter.
*
* @param list list to append payees into
* @return return true if a payee filter has been set
*/
bool payees(QStringList& list) const;
/**
* This method returns whether a tag filter has been set,
* and if so, it returns all the tags set in the filter.
*
* @param list list to append tags into
* @return return true if a tag filter has been set
*/
bool tags(QStringList& list) const;
/**
* This method returns whether an account filter has been set,
* and if so, it returns all the accounts set in the filter.
*
* @param list list to append accounts into
* @return return true if an account filter has been set
*/
bool accounts(QStringList& list) const;
/**
* This method returns whether a category filter has been set,
* and if so, it returns all the categories set in the filter.
*
* @param list list to append categories into
* @return return true if a category filter has been set
*/
bool categories(QStringList& list) const;
/**
* This method returns whether a type filter has been set,
* and if so, it returns the first type in the filter.
*
* @param i int to replace with first type filter, untouched otherwise
* @return return true if a type filter has been set
*/
bool firstType(int& i) const;
bool types(QList<int>& list) const;
/**
* This method returns whether a state filter has been set,
* and if so, it returns the first state in the filter.
*
* @param i reference to int to replace with first state filter, untouched otherwise
* @return return true if a state filter has been set
*/
bool firstState(int& i) const;
bool states(QList<int>& list) const;
/**
* This method returns whether a text filter has been set,
* and if so, it returns the text filter.
*
* @param text regexp to replace with text filter, or blank if none set
* @return return true if a text filter has been set
*/
bool textFilter(QRegExp& text) const;
/**
* This method returns whether the text filter should return
* that DO NOT contain the text
*/
bool isInvertingText() const {
return m_invertText;
};
/**
* This method translates a plain-language date range into QDate
* start & end
*
* @param range Plain-language range of dates, e.g. 'CurrentYear'
* @param start QDate will be set to corresponding to the first date in @p range
* @param end QDate will be set to corresponding to the last date in @p range
* @return return true if a range was successfully set, or false if @p range was invalid
*/
static bool translateDateRange(dateOptionE range, QDate& start, QDate& end);
static void setFiscalYearStart(int firstMonth, int firstDay);
FilterSet filterSet() const {
return m_filterSet;
};
/**
* This member removes all references to object identified by @p id. Used
* to remove objects which are about to be removed from the engine.
*/
void removeReference(const QString& id);
private:
/**
* This is a conversion tool from MyMoneySplit::reconcileFlagE
* to MyMoneyTransactionFilter::stateE types
*
* @param split reference to split in question
*
* @return converted reconcile flag of the split passed as parameter
*/
int splitState(const MyMoneySplit& split) const;
/**
* This is a conversion tool from MyMoneySplit::action
* to MyMoneyTransactionFilter::typeE types
*
* @param t reference to transaction
* @param split reference to split in question
*
* @return converted action of the split passed as parameter
*/
int splitType(const MyMoneyTransaction& t, const MyMoneySplit& split) const;
/**
* This method checks if a transaction is valid or not. A transaction
* is considered valid, if the sum of all splits is zero, invalid otherwise.
*
* @param transaction reference to transaction to be checked
* @retval valid transaction is valid
* @retval invalid transaction is invalid
*/
validityOptionE validTransaction(const MyMoneyTransaction& transaction) const;
protected:
FilterSet m_filterSet;
bool m_reportAllSplits;
bool m_considerCategory;
QRegExp m_text;
bool m_invertText;
QHash<QString, QString> m_accounts;
QHash<QString, QString> m_payees;
QHash<QString, QString> m_tags;
QHash<QString, QString> m_categories;
QHash<int, QString> m_states;
QHash<int, QString> m_types;
QHash<int, QString> m_validity;
QString m_fromNr, m_toNr;
QDate m_fromDate, m_toDate;
MyMoneyMoney m_fromAmount, m_toAmount;
QList<MyMoneySplit> m_matchingSplits;
};
/**
* Make it possible to hold @ref MyMoneyTransactionFilter objects inside @ref QVariant objects.
*/
Q_DECLARE_METATYPE(MyMoneyTransactionFilter)
#endif
diff --git a/kmymoney/mymoney/mymoneyutils.cpp b/kmymoney/mymoney/mymoneyutils.cpp
index 5c63b96d7..612dc3550 100644
--- a/kmymoney/mymoney/mymoneyutils.cpp
+++ b/kmymoney/mymoney/mymoneyutils.cpp
@@ -1,179 +1,180 @@
/***************************************************************************
mymoneyutils.cpp - description
-------------------
begin : Tue Jan 29 2002
copyright : (C) 2000-2002 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "mymoneyutils.h"
#include <iostream>
#include "mymoneyaccount.h"
#include "mymoneymoney.h"
+#include "mymoneysecurity.h"
#include <cstdio>
#include <cstdarg>
#include <QRegExp>
QString MyMoneyUtils::getFileExtension(QString strFileName)
{
QString strTemp;
if (!strFileName.isEmpty()) {
//find last . delminator
int nLoc = strFileName.lastIndexOf('.');
if (nLoc != -1) {
strTemp = strFileName.right(strFileName.length() - (nLoc + 1));
return strTemp.toUpper();
}
}
return strTemp;
}
QString MyMoneyUtils::formatMoney(const MyMoneyMoney& val,
const MyMoneyAccount& acc,
const MyMoneySecurity& sec,
bool showThousandSeparator)
{
return val.formatMoney(sec.tradingSymbol(),
val.denomToPrec(acc.fraction()),
showThousandSeparator);
}
QString MyMoneyUtils::formatMoney(const MyMoneyMoney& val,
const MyMoneySecurity& sec,
bool showThousandSeparator)
{
return val.formatMoney(sec.tradingSymbol(),
val.denomToPrec(sec.smallestAccountFraction()),
showThousandSeparator);
}
int MyMoneyTracer::m_indentLevel = 0;
int MyMoneyTracer::m_onoff = 0;
MyMoneyTracer::MyMoneyTracer(const char* name)
{
if (m_onoff) {
QRegExp exp("(.*)::(.*)");
if (exp.indexIn(name) != -1) {
m_className = exp.cap(1);
m_memberName = exp.cap(2);
} else {
m_className = QString(name);
m_memberName.clear();
}
QString indent;
indent.fill(' ', m_indentLevel);
std::cerr << qPrintable(indent) << "ENTER: " << qPrintable(m_className) << "::" << qPrintable(m_memberName) << std::endl;
}
m_indentLevel += 2;
}
MyMoneyTracer::MyMoneyTracer(const QString& className, const QString& memberName) :
m_className(className),
m_memberName(memberName)
{
if (m_onoff) {
QString indent;
indent.fill(' ', m_indentLevel);
std::cerr << qPrintable(indent) << "ENTER: " << qPrintable(m_className) << "::" << qPrintable(m_memberName) << std::endl;
}
m_indentLevel += 2;
}
MyMoneyTracer::~MyMoneyTracer()
{
m_indentLevel -= 2;
if (m_onoff) {
QString indent;
indent.fill(' ', m_indentLevel);
std::cerr << qPrintable(indent) << "LEAVE: " << qPrintable(m_className) << "::" << qPrintable(m_memberName) << std::endl;
}
}
void MyMoneyTracer::printf(const char *format, ...) const
{
if (m_onoff) {
va_list args;
va_start(args, format);
QString indent;
indent.fill(' ', m_indentLevel);
std::cerr << qPrintable(indent);
vfprintf(stderr, format, args);
putc('\n', stderr);
va_end(args);
}
}
void MyMoneyTracer::onOff(int onOff)
{
m_onoff = onOff;
}
void MyMoneyTracer::on()
{
m_onoff = 1;
}
void MyMoneyTracer::off()
{
m_onoff = 0;
}
QString dateToString(const QDate& date)
{
if (!date.isNull() && date.isValid())
return date.toString(Qt::ISODate);
return QString();
}
QDate stringToDate(const QString& str)
{
if (str.length()) {
QDate date = QDate::fromString(str, Qt::ISODate);
if (!date.isNull() && date.isValid())
return date;
}
return QDate();
}
QString QStringEmpty(const QString& val)
{
if (!val.isEmpty())
return QString(val);
return QString();
}
unsigned long extractId(const QString& txt)
{
int pos;
unsigned long rc = 0;
pos = txt.indexOf(QRegExp("\\d+"), 0);
if (pos != -1) {
rc = txt.mid(pos).toInt();
}
return rc;
}
diff --git a/kmymoney/mymoney/payeeidentifiermodel.cpp b/kmymoney/mymoney/payeeidentifiermodel.cpp
index 4afa6f352..907b0418b 100644
--- a/kmymoney/mymoney/payeeidentifiermodel.cpp
+++ b/kmymoney/mymoney/payeeidentifiermodel.cpp
@@ -1,160 +1,161 @@
/*
* This file is part of KMyMoney, A Personal Finance Manager by KDE
* Copyright (C) 2015, 2016 Christian David <christian-david@web.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "payeeidentifiermodel.h"
#include <limits>
#include <KLocalizedString>
#include "mymoneyfile.h"
+#include "mymoneyexception.h"
/**
* @brief create unique value for QModelIndex::internalId() to indicate "not set"
*/
static constexpr decltype(reinterpret_cast<QModelIndex*>(0)->internalId()) invalidParent = std::numeric_limits<decltype(reinterpret_cast<QModelIndex*>(0)->internalId())>::max();
payeeIdentifierModel::payeeIdentifierModel(QObject* parent)
: QAbstractItemModel(parent),
m_payeeIdentifierIds(),
m_typeFilter()
{
}
void payeeIdentifierModel::setTypeFilter(QStringList filter)
{
m_typeFilter = filter;
loadData();
}
void payeeIdentifierModel::setTypeFilter(QString type)
{
setTypeFilter(QStringList(type));
}
void payeeIdentifierModel::loadData()
{
beginResetModel();
const QList<MyMoneyPayee> payees = MyMoneyFile::instance()->payeeList();
m_payeeIdentifierIds.clear();
m_payeeIdentifierIds.reserve(payees.count());
Q_FOREACH(const MyMoneyPayee& payee, payees) {
m_payeeIdentifierIds.append(payee.id());
}
endResetModel();
}
MyMoneyPayee payeeIdentifierModel::payeeByIndex(const QModelIndex& index) const
{
if (index.isValid() && index.row() >= 0 && index.row() < m_payeeIdentifierIds.count()) {
try {
return MyMoneyFile::instance()->payee(m_payeeIdentifierIds.at(index.row()));
} catch (MyMoneyException&) {
}
}
return MyMoneyPayee();
}
QVariant payeeIdentifierModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
const bool isPayeeIdentifier = index.parent().isValid();
if (role == payeeIdentifierModel::isPayeeIdentifier)
return isPayeeIdentifier;
const MyMoneyPayee payee = (isPayeeIdentifier) ? payeeByIndex(index.parent()) : payeeByIndex(index);
if (role == payeeName || (!isPayeeIdentifier && role == Qt::DisplayRole)) {
// Return data for MyMoneyPayee
return payee.name();
} else if (isPayeeIdentifier) {
// Return data for payeeIdentifier
if (index.row() >= 0 && static_cast<unsigned int>(index.row()) < payee.payeeIdentifierCount()) {
::payeeIdentifier ident = payee.payeeIdentifier(index.row());
if (role == payeeIdentifier) {
return QVariant::fromValue< ::payeeIdentifier >(ident);
} else if (ident.isNull()) {
return QVariant();
} else if (role == payeeIdentifierType) {
return ident.iid();
} else if (role == Qt::DisplayRole) {
// The custom delegates won't ask for this role
return QVariant::fromValue(i18n("The plugin to show this information could not be found."));
}
}
}
return QVariant();
}
Qt::ItemFlags payeeIdentifierModel::flags(const QModelIndex &index) const
{
Q_UNUSED(index)
#if 0
if (!index.parent().isValid()) {
if (payeeByIndex(index).payeeIdentifierCount() > 0)
return Qt::ItemIsEnabled;
}
#endif
return (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
}
/**
* @intenal The internalId of QModelIndex is set to the row of the parent or invalidParent if there is no
* parent.
*
* @todo Qt5: the type of the internal id changed!
*/
QModelIndex payeeIdentifierModel::index(int row, int column, const QModelIndex &parent) const
{
if (parent.isValid())
return createIndex(row, column, parent.row());
return createIndex(row, column, invalidParent);
}
int payeeIdentifierModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return 1;
}
int payeeIdentifierModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid()) {
if (parent.internalId() != invalidParent)
return 0;
return payeeByIndex(parent).payeeIdentifierCount();
}
return m_payeeIdentifierIds.count();
}
QModelIndex payeeIdentifierModel::parent(const QModelIndex& child) const
{
if (child.internalId() != invalidParent)
return createIndex(child.internalId(), 0, invalidParent);
return QModelIndex();
}
diff --git a/kmymoney/mymoney/storage/imymoneyserialize.h b/kmymoney/mymoney/storage/imymoneyserialize.h
index 83c448bf7..af331f041 100644
--- a/kmymoney/mymoney/storage/imymoneyserialize.h
+++ b/kmymoney/mymoney/storage/imymoneyserialize.h
@@ -1,404 +1,404 @@
/***************************************************************************
imymoneyserialize.h - description
-------------------
begin : Fri May 10 2002
copyright : (C) 2000-2002 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 IMYMONEYSERIALIZE_H
#define IMYMONEYSERIALIZE_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QString>
#include <QList>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyaccount.h"
#include "mymoneypayee.h"
#include "mymoneyschedule.h"
#include "mymoneyprice.h"
/**
* @author Thomas Baumgart
*/
/**
* This class represents the interface to serialize a MyMoneyStorage object
*/
class MyMoneyBudget;
class MyMoneyCostCenter;
class MyMoneyInstitution;
class MyMoneyReport;
class MyMoneySecurity;
class MyMoneyStorageSql;
class MyMoneyTag;
class MyMoneyTransaction;
class MyMoneyTransactionFilter;
class onlineJob;
class IMyMoneySerialize
{
public:
IMyMoneySerialize();
virtual ~IMyMoneySerialize();
// general get functions
virtual const MyMoneyPayee& user() const = 0;
virtual const QDate creationDate() const = 0;
virtual const QDate lastModificationDate() const = 0;
virtual unsigned int currentFixVersion() const = 0;
virtual unsigned int fileFixVersion() const = 0;
// general set functions
virtual void setUser(const MyMoneyPayee& val) = 0;
virtual void setCreationDate(const QDate& val) = 0;
virtual void setFileFixVersion(const unsigned int v) = 0;
/**
* This method is used to get a SQL reader for subsequent database access
*/
virtual QExplicitlySharedDataPointer <MyMoneyStorageSql> 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<MyMoneyAccount>& list) const = 0;
/**
* This method returns a list of the institutions
* inside a MyMoneyStorage object
*
* @return QMap containing the institution information
*/
virtual const QList<MyMoneyInstitution> 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<MyMoneyTransaction> receiving
* the set of transactions
* @param filter MyMoneyTransactionFilter object with the match criteria
*/
virtual void transactionList(QList<MyMoneyTransaction>& 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<MyMoneyPayee> containing the payee information
*/
virtual const QList<MyMoneyPayee> payeeList() const = 0;
/**
* This method returns a list of the tags
* inside a MyMoneyStorage object
*
* @return QList<MyMoneyTag> containing the tag information
*/
virtual const QList<MyMoneyTag> 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 const QList<MyMoneySchedule> scheduleList(const QString& = QString(),
- const MyMoneySchedule::typeE = MyMoneySchedule::TYPE_ANY,
- const MyMoneySchedule::occurrenceE = MyMoneySchedule::OCCUR_ANY,
- const MyMoneySchedule::paymentTypeE = MyMoneySchedule::STYPE_ANY,
+ const eMyMoney::Schedule::Type = eMyMoney::Schedule::Type::Any,
+ const eMyMoney::Schedule::Occurrence = eMyMoney::Schedule::Occurrence::Any,
+ const eMyMoney::Schedule::PaymentType = eMyMoney::Schedule::PaymentType::Any,
const QDate& = QDate(),
const QDate& = QDate(),
const bool = false) const = 0;
/**
* This method returns a list of security objects that the engine has
* knowledge of.
*/
virtual const QList<MyMoneySecurity> securityList() const = 0;
/**
* This method returns a list of onlineJobs the engine has
*/
virtual const QList<onlineJob> onlineJobList() const = 0;
/**
* This method returns a list of cost center objects the engine knows about
*/
virtual const QList<MyMoneyCostCenter> costCenterList() const = 0;
/**
* This method is used to return the standard liability account
* @return MyMoneyAccount liability account(group)
*/
virtual const MyMoneyAccount liability() const = 0;
/**
* This method is used to return the standard asset account
* @return MyMoneyAccount asset account(group)
*/
virtual const MyMoneyAccount asset() const = 0;
/**
* This method is used to return the standard expense account
* @return MyMoneyAccount expense account(group)
*/
virtual const MyMoneyAccount expense() const = 0;
/**
* This method is used to return the standard income account
* @return MyMoneyAccount income account(group)
*/
virtual const MyMoneyAccount income() const = 0;
/**
* This method is used to return the standard equity account
* @return MyMoneyAccount equity account(group)
*/
virtual const 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, const bool skipAccountUpdate = false) = 0;
virtual void loadAccounts(const QMap<QString, MyMoneyAccount>& map) = 0;
virtual void loadTransactions(const QMap<QString, MyMoneyTransaction>& map) = 0;
virtual void loadInstitutions(const QMap<QString, MyMoneyInstitution>& map) = 0;
virtual void loadPayees(const QMap<QString, MyMoneyPayee>& map) = 0;
virtual void loadTags(const QMap<QString, MyMoneyTag>& map) = 0;
virtual void loadSchedules(const QMap<QString, MyMoneySchedule>& map) = 0;
virtual void loadSecurities(const QMap<QString, MyMoneySecurity>& map) = 0;
virtual void loadCurrencies(const QMap<QString, MyMoneySecurity>& map) = 0;
virtual void loadReports(const QMap<QString, MyMoneyReport>& reports) = 0;
virtual void loadBudgets(const QMap<QString, MyMoneyBudget>& budgets) = 0;
virtual void loadPrices(const MyMoneyPriceList& list) = 0;
virtual void loadOnlineJobs(const QMap<QString, onlineJob>& onlineJobs) = 0;
virtual void loadCostCenters(const QMap<QString, MyMoneyCostCenter>& costCenters) = 0;
virtual unsigned long accountId() const = 0;
virtual unsigned long transactionId() const = 0;
virtual unsigned long payeeId() const = 0;
virtual unsigned long tagId() const = 0;
virtual unsigned long institutionId() const = 0;
virtual unsigned long scheduleId() const = 0;
virtual unsigned long securityId() const = 0;
virtual unsigned long reportId() const = 0;
virtual unsigned long budgetId() const = 0;
virtual unsigned long onlineJobId() const = 0;
virtual unsigned long costCenterId() const = 0;
virtual void loadAccountId(const unsigned long id) = 0;
virtual void loadTransactionId(const unsigned long id) = 0;
virtual void loadPayeeId(const unsigned long id) = 0;
virtual void loadTagId(const unsigned long id) = 0;
virtual void loadInstitutionId(const unsigned long id) = 0;
virtual void loadScheduleId(const unsigned long id) = 0;
virtual void loadSecurityId(const unsigned long id) = 0;
virtual void loadReportId(const unsigned long id) = 0;
virtual void loadBudgetId(const unsigned long id) = 0;
virtual void loadOnlineJobId(const unsigned long id) = 0;
virtual void loadCostCenterId(const unsigned long 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<QString, QString> containing all key/value pairs of
* this container.
*/
virtual const QMap<QString, QString> 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<QString, QString> 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<QString, QString>& list) = 0;
virtual const QList<MyMoneySchedule> scheduleListEx(int scheduleTypes,
int scheduleOcurrences,
int schedulePaymentTypes,
QDate startDate,
const QStringList& accounts = QStringList()) 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 const QList<MyMoneySecurity> 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 const QList<MyMoneyReport> 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 const QList<MyMoneyBudget> 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 const MyMoneyPriceList priceList() const = 0;
/**
* This method recalculates the balances of all accounts
* based on the transactions stored in the engine.
*/
virtual void rebuildAccountBalances() = 0;
};
#endif
diff --git a/kmymoney/mymoney/storage/imymoneystorage.h b/kmymoney/mymoney/storage/imymoneystorage.h
index 53f8ddf1c..5704bbbf7 100644
--- a/kmymoney/mymoney/storage/imymoneystorage.h
+++ b/kmymoney/mymoney/storage/imymoneystorage.h
@@ -1,951 +1,951 @@
/***************************************************************************
imymoneystorage.h - description
-------------------
begin : Sun May 5 2002
copyright : (C) 2000-2002 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 IMYMONEYSTORAGE_H
#define IMYMONEYSTORAGE_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QString>
#include <QList>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyinstitution.h"
#include "mymoneyaccount.h"
#include "mymoneytransaction.h"
#include "mymoneypayee.h"
#include "mymoneytag.h"
#include "mymoneyschedule.h"
#include "mymoneysecurity.h"
#include "mymoneyprice.h"
#include "mymoneyreport.h"
#include <mymoneybudget.h>
#include <onlinejob.h>
#include <mymoneycostcenter.h>
class MyMoneySplit;
class MyMoneyTransactionFilter;
/**
* @author Thomas Baumgart
*/
/**
* The IMyMoneyStorage class describes the interface between the MyMoneyFile class
* and the real storage manager.
*
* @see MyMoneySeqAccessMgr
*/
class IMyMoneyStorage
{
public:
// definitions for the ID's of the standard accounts
#define STD_ACC_LIABILITY "AStd::Liability"
#define STD_ACC_ASSET "AStd::Asset"
#define STD_ACC_EXPENSE "AStd::Expense"
#define STD_ACC_INCOME "AStd::Income"
#define STD_ACC_EQUITY "AStd::Equity"
IMyMoneyStorage();
virtual ~IMyMoneyStorage();
// general get functions
virtual const MyMoneyPayee& user() const = 0;
virtual const QDate creationDate() const = 0;
virtual const QDate lastModificationDate() const = 0;
virtual unsigned int currentFixVersion() const = 0;
virtual unsigned int fileFixVersion() const = 0;
// general set functions
virtual void setUser(const MyMoneyPayee& user) = 0;
virtual void setFileFixVersion(const unsigned int v) = 0;
// methods provided by MyMoneyKeyValueContainer
virtual void setValue(const QString& key, const QString& value) = 0;
virtual const 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 const 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 const 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<MyMoneyPayee> containing the payee information
*/
virtual const QList<MyMoneyPayee> 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 const 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 const 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<MyMoneyTag> containing the tag information
*/
virtual const QList<MyMoneyTag> 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 const 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, const 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 const 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 const 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 const 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 unsigned int accountCount() const = 0;
/**
* This method returns a list of the institutions
* inside a MyMoneyStorage object
*
* @return QList<MyMoneyInstitution> containing the
* institution information
*/
virtual const QList<MyMoneyInstitution> 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, const 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 unsigned int transactionCount(const QString& account = QString()) 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 const QMap<QString, unsigned long> 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<MyMoneyTransaction>
*/
virtual const QList<MyMoneyTransaction> transactionList(MyMoneyTransactionFilter& filter) const = 0;
virtual void transactionList(QList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const = 0;
virtual void transactionList(QList<QPair<MyMoneyTransaction, MyMoneySplit> >& 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 const 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 const 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 unsigned int 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<MyMoneyAccount>& list) const = 0;
/**
* This method is used to return the standard liability account
* @return MyMoneyAccount liability account(group)
*/
virtual const MyMoneyAccount liability() const = 0;
/**
* This method is used to return the standard asset account
* @return MyMoneyAccount asset account(group)
*/
virtual const MyMoneyAccount asset() const = 0;
/**
* This method is used to return the standard expense account
* @return MyMoneyAccount expense account(group)
*/
virtual const MyMoneyAccount expense() const = 0;
/**
* This method is used to return the standard income account
* @return MyMoneyAccount income account(group)
*/
virtual const MyMoneyAccount income() const = 0;
/**
* This method is used to return the standard equity account
* @return MyMoneyAccount equity account(group)
*/
virtual const 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 const MyMoneySecurity security(const QString& id) const = 0;
/**
* This method returns a list of the security objects
* inside a MyMoneyStorage object
*
* @return QList<MyMoneySecurity> containing objects
*/
virtual const QList<MyMoneySecurity> 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, const bool exactDate) const = 0;
/**
* This method returns a list of all prices.
*
* @return MyMoneyPriceList of all MyMoneyPrice objects.
*/
virtual const 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 const 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 MyMoneySchedule::typeE for details.
- * Default is MyMoneySchedule::TYPE_ANY
+ * 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 MyMoneySchedule::occurrenceE for details.
- * Default is MyMoneySchedule::OCCUR_ANY
+ * See eMyMoney::Schedule::Occurence for details.
+ * Default is eMyMoney::Schedule::Occurrence::Any
* @param paymentType only schedules of payment method @p paymentType
* are searched for.
- * See MyMoneySchedule::paymentTypeE for details.
- * Default is MyMoneySchedule::STYPE_ANY
+ * 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<MyMoneySchedule> list of schedule objects.
*/
virtual const QList<MyMoneySchedule> scheduleList(const QString& accountId = QString(),
- const MyMoneySchedule::typeE type = MyMoneySchedule::TYPE_ANY,
- const MyMoneySchedule::occurrenceE occurrence = MyMoneySchedule::OCCUR_ANY,
- const MyMoneySchedule::paymentTypeE paymentType = MyMoneySchedule::STYPE_ANY,
+ const eMyMoney::Schedule::Type type = eMyMoney::Schedule::Type::Any,
+ const eMyMoney::Schedule::Occurrence occurrence = eMyMoney::Schedule::Occurrence::Any,
+ const eMyMoney::Schedule::PaymentType paymentType = eMyMoney::Schedule::PaymentType::Any,
const QDate& startDate = QDate(),
const QDate& endDate = QDate(),
const bool overdue = false) const = 0;
virtual const QList<MyMoneySchedule> scheduleListEx(int scheduleTypes,
int scheduleOcurrences,
int schedulePaymentTypes,
QDate startDate,
const QStringList& accounts = QStringList()) 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 const 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 const QList<MyMoneySecurity> 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 const QList<MyMoneyReport> 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 unsigned 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 const 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 const QList<MyMoneyBudget> 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 const 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 unsigned 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 const onlineJob getOnlineJob(const QString& jobId) const = 0;
/**
* @brief Return all onlineJobs
*/
virtual const QList<onlineJob> onlineJobList() const = 0;
/**
* @brief Remove an onlineJobs
*/
virtual void removeOnlineJob(const onlineJob&) = 0;
/**
* @brief Returns a cost center by id
*/
virtual const MyMoneyCostCenter costCenter(const QString& id) const = 0;
/**
* @brief Retruns a list of all costcenters
*/
virtual const QList<MyMoneyCostCenter> 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/mymoneydatabasemgr.cpp b/kmymoney/mymoney/storage/mymoneydatabasemgr.cpp
index c4590436b..6228e924c 100644
--- a/kmymoney/mymoney/storage/mymoneydatabasemgr.cpp
+++ b/kmymoney/mymoney/storage/mymoneydatabasemgr.cpp
@@ -1,2327 +1,2327 @@
/***************************************************************************
mymoneydatabasemgr.cpp
-------------------
begin : June 5 2007
copyright : (C) 2007 by Fernando Vilas
email : Fernando Vilas <fvilas@iname.com>
2017 by Łukasz Wojniłowicz <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 "mymoneydatabasemgr.h"
#include <algorithm>
#include <typeinfo>
// ----------------------------------------------------------------------------
// QT Includes
#include <QBitArray>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "../mymoneytransactionfilter.h"
#include "../mymoneycategory.h"
#include "mymoneyfile.h"
#include "mymoneymap.h"
#include "storageenums.h"
using namespace eStorage;
MyMoneyDatabaseMgr::MyMoneyDatabaseMgr() :
m_creationDate(QDate::currentDate()),
m_currentFixVersion(0),
m_fileFixVersion(0),
m_lastModificationDate(QDate::currentDate()),
m_sql(0)
{ }
MyMoneyDatabaseMgr::~MyMoneyDatabaseMgr()
{ }
// general get functions
const MyMoneyPayee& MyMoneyDatabaseMgr::user() const
{
return m_user;
}
const QDate MyMoneyDatabaseMgr::creationDate() const
{
return m_creationDate;
}
const QDate MyMoneyDatabaseMgr::lastModificationDate() const
{
return m_lastModificationDate;
}
unsigned int MyMoneyDatabaseMgr::currentFixVersion() const
{
return CURRENT_FIX_VERSION;
}
unsigned int MyMoneyDatabaseMgr::fileFixVersion() const
{
return m_fileFixVersion;
}
// general set functions
void MyMoneyDatabaseMgr::setUser(const MyMoneyPayee& user)
{
m_user = user;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
m_sql->modifyUserInfo(user);
}
}
void MyMoneyDatabaseMgr::setFileFixVersion(const unsigned int v)
{
m_fileFixVersion = v;
}
// methods provided by MyMoneyKeyValueContainer
const 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);
}
const QMap<QString, QString> MyMoneyDatabaseMgr::pairs() const
{
return MyMoneyKeyValueContainer::pairs();
}
void MyMoneyDatabaseMgr::setPairs(const QMap<QString, QString>& list)
{
MyMoneyKeyValueContainer::setPairs(list);
}
MyMoneyDatabaseMgr const * MyMoneyDatabaseMgr::duplicate()
{
MyMoneyDatabaseMgr* that = new MyMoneyDatabaseMgr();
*that = *this;
return that;
}
void MyMoneyDatabaseMgr::addAccount(MyMoneyAccount& account)
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
// create the account.
MyMoneyAccount newAccount(nextAccountID(), account);
m_sql->addAccount(newAccount);
account = newAccount;
}
}
void MyMoneyDatabaseMgr::addAccount(MyMoneyAccount& parent, MyMoneyAccount& account)
{
QMap<QString, MyMoneyAccount> accountList;
QStringList accountIdList;
QMap<QString, MyMoneyAccount>::ConstIterator theParent;
QMap<QString, MyMoneyAccount>::ConstIterator theChild;
accountIdList << parent.id() << account.id();
startTransaction();
accountList = 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<MyMoneyAccount> aList;
aList << parent << account;
m_sql->modifyAccountList(aList);
commitTransaction();
}
void MyMoneyDatabaseMgr::addPayee(MyMoneyPayee& payee)
{
if (m_sql) {
if (! m_sql->isOpen())
static_cast<QSqlDatabase*>(m_sql.data())->open();
// create the payee
MyMoneyPayee newPayee(nextPayeeID(), payee);
m_sql->addPayee(newPayee);
payee = newPayee;
}
}
const MyMoneyPayee MyMoneyDatabaseMgr::payee(const QString& id) const
{
QMap<QString, MyMoneyPayee>::ConstIterator it;
QMap<QString, MyMoneyPayee> payeeList = m_sql->fetchPayees(QStringList(id));
it = payeeList.constFind(id);
if (it == payeeList.constEnd())
throw MYMONEYEXCEPTION("Unknown payee '" + id + '\'');
return *it;
}
const MyMoneyPayee MyMoneyDatabaseMgr::payeeByName(const QString& payee) const
{
if (payee.isEmpty())
return MyMoneyPayee::null;
QMap<QString, MyMoneyPayee> payeeList;
try {
payeeList = m_sql->fetchPayees();
} catch (const MyMoneyException &) {
throw;
}
QMap<QString, MyMoneyPayee>::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)
{
QMap<QString, MyMoneyPayee> payeeList = m_sql->fetchPayees(QStringList(payee.id()), true);
QMap<QString, MyMoneyPayee>::ConstIterator it;
it = payeeList.constFind(payee.id());
if (it == payeeList.constEnd()) {
QString msg = "Unknown payee '" + payee.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_sql->modifyPayee(payee);
}
void MyMoneyDatabaseMgr::removePayee(const MyMoneyPayee& payee)
{
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
QMap<QString, MyMoneySchedule>::ConstIterator it_s;
QMap<QString, MyMoneyPayee> payeeList = m_sql->fetchPayees(QStringList(payee.id()));
QMap<QString, MyMoneyPayee>::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<QString, MyMoneyTransaction> transactionList = 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<QString, MyMoneySchedule> scheduleList = 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
removeReferences(payee.id());
m_sql->removePayee(payee);
}
const QList<MyMoneyPayee> MyMoneyDatabaseMgr::payeeList() const
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
return m_sql->fetchPayees().values();
} else
return QList<MyMoneyPayee> ();
}
void MyMoneyDatabaseMgr::addTag(MyMoneyTag& tag)
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
// create the tag
MyMoneyTag newTag(nextTagID(), tag);
m_sql->addTag(newTag);
tag = newTag;
}
}
const MyMoneyTag MyMoneyDatabaseMgr::tag(const QString& id) const
{
QMap<QString, MyMoneyTag>::ConstIterator it;
QMap<QString, MyMoneyTag> tagList = m_sql->fetchTags(QStringList(id));
it = tagList.constFind(id);
if (it == tagList.constEnd())
throw MYMONEYEXCEPTION("Unknown tag '" + id + '\'');
return *it;
}
const MyMoneyTag MyMoneyDatabaseMgr::tagByName(const QString& tag) const
{
if (tag.isEmpty())
return MyMoneyTag::null;
QMap<QString, MyMoneyTag> tagList;
try {
tagList = m_sql->fetchTags();
} catch (const MyMoneyException &) {
throw;
}
QMap<QString, MyMoneyTag>::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)
{
QMap<QString, MyMoneyTag> tagList = m_sql->fetchTags(QStringList(tag.id()), true);
QMap<QString, MyMoneyTag>::ConstIterator it;
it = tagList.constFind(tag.id());
if (it == tagList.constEnd()) {
QString msg = "Unknown tag '" + tag.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_sql->modifyTag(tag);
}
void MyMoneyDatabaseMgr::removeTag(const MyMoneyTag& tag)
{
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
QMap<QString, MyMoneySchedule>::ConstIterator it_s;
QMap<QString, MyMoneyTag> tagList = m_sql->fetchTags(QStringList(tag.id()));
QMap<QString, MyMoneyTag>::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<QString, MyMoneyTransaction> transactionList = 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<QString, MyMoneySchedule> scheduleList = 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
removeReferences(tag.id());
m_sql->removeTag(tag);
}
const QList<MyMoneyTag> MyMoneyDatabaseMgr::tagList() const
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
return m_sql->fetchTags().values();
} else {
return QList<MyMoneyTag> ();
}
}
void MyMoneyDatabaseMgr::modifyOnlineJob(const onlineJob& job)
{
if (job.id().isEmpty())
throw MYMONEYEXCEPTION("empty online job id");
m_sql->modifyOnlineJob(job);
}
void MyMoneyDatabaseMgr::addOnlineJob(onlineJob& job)
{
job = onlineJob(nextOnlineJobID(), job);
m_sql->addOnlineJob(job);
}
const onlineJob MyMoneyDatabaseMgr::getOnlineJob(const QString &jobId) const
{
if (jobId.isEmpty())
throw MYMONEYEXCEPTION("empty online job id");
if (m_sql) {
if (! m_sql->isOpen())
((QSqlDatabase*)(m_sql.data()))->open();
QMap <QString, onlineJob> jobList = m_sql->fetchOnlineJobs(QStringList(jobId));
QMap <QString, onlineJob>::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('\''));
}
const QList<onlineJob> MyMoneyDatabaseMgr::onlineJobList() const
{
if (m_sql) {
if (!m_sql->isOpen())
((QSqlDatabase*)(m_sql.data()))->open();
return m_sql->fetchOnlineJobs().values();
}
return QList<onlineJob>();
}
void MyMoneyDatabaseMgr::removeOnlineJob(const onlineJob& job)
{
if (job.id().isEmpty())
throw MYMONEYEXCEPTION("Empty online job id during remove.");
m_sql->removeOnlineJob(job);
}
const MyMoneyAccount MyMoneyDatabaseMgr::account(const QString& id) const
{
if (id.isEmpty()) {
throw MYMONEYEXCEPTION("empty account id");
}
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
QMap <QString, MyMoneyAccount> accountList = m_sql->fetchAccounts(QStringList(id));
QMap <QString, MyMoneyAccount>::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 == STD_ACC_LIABILITY
|| id == STD_ACC_ASSET
|| id == STD_ACC_EXPENSE
|| id == STD_ACC_INCOME
|| id == STD_ACC_EQUITY;
}
void MyMoneyDatabaseMgr::setAccountName(const QString& id, const QString& name)
{
if (!isStandardAccount(id))
throw MYMONEYEXCEPTION("Only standard accounts can be modified using setAccountName()");
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
startTransaction();
MyMoneyAccount acc = m_sql->fetchAccounts(QStringList(id), true)[id];
acc.setName(name);
m_sql->modifyAccount(acc);
commitTransaction();
}
}
void MyMoneyDatabaseMgr::addInstitution(MyMoneyInstitution& institution)
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
MyMoneyInstitution newInstitution(nextInstitutionID(), institution);
// mark file as changed
m_sql->addInstitution(newInstitution);
// return new data
institution = newInstitution;
}
}
const QString MyMoneyDatabaseMgr::nextPayeeID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(ulong(m_sql->incrementPayeeId()));
id = 'P' + id.rightJustified(PAYEE_ID_SIZE, '0');
}
return id;
}
const QString MyMoneyDatabaseMgr::nextTagID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(ulong(m_sql->incrementTagId()));
id = 'G' + id.rightJustified(TAG_ID_SIZE, '0');
}
return id;
}
const QString MyMoneyDatabaseMgr::nextInstitutionID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(ulong(m_sql->incrementInstitutionId()));
id = 'I' + id.rightJustified(INSTITUTION_ID_SIZE, '0');
}
return id;
}
const QString MyMoneyDatabaseMgr::nextAccountID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(ulong(m_sql->incrementAccountId()));
id = 'A' + id.rightJustified(ACCOUNT_ID_SIZE, '0');
}
return id;
}
const QString MyMoneyDatabaseMgr::nextBudgetID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(ulong(m_sql->incrementBudgetId()));
id = 'B' + id.rightJustified(BUDGET_ID_SIZE, '0');
}
return id;
}
const QString MyMoneyDatabaseMgr::nextReportID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(ulong(m_sql->incrementReportId()));
id = 'R' + id.rightJustified(REPORT_ID_SIZE, '0');
}
return id;
}
const QString MyMoneyDatabaseMgr::nextTransactionID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(ulong(m_sql->incrementTransactionId()));
id = 'T' + id.rightJustified(TRANSACTION_ID_SIZE, '0');
}
return id;
}
const QString MyMoneyDatabaseMgr::nextScheduleID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(ulong(m_sql->incrementScheduleId()));
id = "SCH" + id.rightJustified(SCHEDULE_ID_SIZE, '0');
}
return id;
}
const QString MyMoneyDatabaseMgr::nextSecurityID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(ulong(m_sql->incrementSecurityId()));
id = 'E' + id.rightJustified(SECURITY_ID_SIZE, '0');
}
return id;
}
const QString MyMoneyDatabaseMgr::nextOnlineJobID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(m_sql->incrementOnlineJobId());
id = QLatin1Char('O') + id.rightJustified(ONLINEJOB_ID_SIZE, '0');
}
return id;
}
const QString MyMoneyDatabaseMgr::nextPayeeIdentifierID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(m_sql->incrementPayeeIdentfierId());
id = QLatin1String("IDENT") + id.rightJustified(PAYEEIDENTIFIER_ID_SIZE, '0');
}
return id;
}
const QString MyMoneyDatabaseMgr::nextCostCenterID()
{
QString id;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
id.setNum(m_sql->incrementCostCenterId());
id = QLatin1Char('C') + id.rightJustified(COSTCENTER_ID_SIZE, '0');
}
return id;
}
void MyMoneyDatabaseMgr::addTransaction(MyMoneyTransaction& transaction, const bool skipAccountUpdate)
{
// 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();
m_sql->addTransaction(newTransaction);
transaction = newTransaction;
QList<MyMoneyAccount> 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;
}
m_sql->modifyAccountList(aList);
}
bool MyMoneyDatabaseMgr::hasActiveSplits(const QString& id) const
{
QMap<QString, MyMoneyTransaction>::ConstIterator it;
MyMoneyTransactionFilter f(id);
QMap<QString, MyMoneyTransaction> transactionList = 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
*/
//const MyMoneyMoney MyMoneyDatabaseMgr::balance(const QString& id, const QDate& date);
const MyMoneyMoney MyMoneyDatabaseMgr::totalBalance(const QString& id, const QDate& date) const
{
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<QString, bool> tempList;
foreach (const QString& it_a, accounts) {
tempList[it_a] = true;
}
accounts = tempList.uniqueKeys();
QMap<QString, MyMoneyMoney> balanceMap = m_sql->fetchBalance(accounts, date);
for (QMap<QString, MyMoneyMoney>::ConstIterator it_b = balanceMap.constBegin(); it_b != balanceMap.constEnd(); ++it_b) {
result += it_b.value();
}
return result;
}
const MyMoneyInstitution MyMoneyDatabaseMgr::institution(const QString& id) const
{
QMap<QString, MyMoneyInstitution>::ConstIterator pos;
QMap<QString, MyMoneyInstitution> institutionList = 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()
{}
unsigned int MyMoneyDatabaseMgr::accountCount() const
{
return m_sql->getRecCount("kmmAccounts");
}
const QList<MyMoneyInstitution> MyMoneyDatabaseMgr::institutionList() const
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
return m_sql->fetchInstitutions().values();
} else {
return QList<MyMoneyInstitution> ();
}
}
void MyMoneyDatabaseMgr::modifyAccount(const MyMoneyAccount& account, const bool skipCheck)
{
QMap<QString, MyMoneyAccount>::ConstIterator pos;
// locate the account in the file global pool
startTransaction();
QMap<QString, MyMoneyAccount> accountList = 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
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)
{
QMap<QString, MyMoneyInstitution> institutionList = m_sql->fetchInstitutions(QStringList(institution.id()));
QMap<QString, MyMoneyInstitution>::ConstIterator pos;
// locate the institution in the file global pool
pos = institutionList.constFind(institution.id());
if (pos != institutionList.constEnd()) {
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)
{
QMap<QString, bool> 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 <QString, MyMoneyTransaction> transactionList = m_sql->fetchTransactions("('" + transaction.id() + "')");
// if(transactionList.size() != 1)
// throw MYMONEYEXCEPTION("invalid transaction key");
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
// it_t = transactionList.find(oldKey);
it_t = transactionList.constBegin();
if (it_t == transactionList.constEnd())
throw MYMONEYEXCEPTION("invalid transaction key");
m_sql->modifyTransaction(transaction);
// mark all accounts referenced in old and new transaction data
// as modified
QMap<QString, MyMoneyAccount> accountList = m_sql->fetchAccounts();
QList<MyMoneyAccount> 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;
}
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;
}
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)
{
- if (account.accountType() == MyMoneyAccount::Stock && parent.accountType() != MyMoneyAccount::Investment)
+ if (account.accountType() == eMyMoney::Account::Stock && parent.accountType() != eMyMoney::Account::Investment)
throw MYMONEYEXCEPTION("Cannot move a stock acocunt into a non-investment account");
QStringList accountIdList;
QMap<QString, MyMoneyAccount>::ConstIterator oldParent;
QMap<QString, MyMoneyAccount>::ConstIterator newParent;
QMap<QString, MyMoneyAccount>::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<QString, MyMoneyAccount> accountList = 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<MyMoneyAccount> 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;
m_sql->modifyAccountList(aList);
commitTransaction();
}
void MyMoneyDatabaseMgr::removeTransaction(const MyMoneyTransaction& transaction)
{
QMap<QString, bool> modifiedAccounts;
// first perform all the checks
if (transaction.id().isEmpty())
throw MYMONEYEXCEPTION("invalid transaction to be deleted");
QMap<QString, QString>::ConstIterator it_k;
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
// it_k = m_transactionKeys.find(transaction.id());
// if(it_k == m_transactionKeys.end())
// throw MYMONEYEXCEPTION("invalid transaction to be deleted");
QMap <QString, MyMoneyTransaction> transactionList = 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<QString, MyMoneyAccount> accountList = m_sql->fetchAccounts();
QList<MyMoneyAccount> 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());
}
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
m_sql->removeTransaction(transaction);
}
unsigned int MyMoneyDatabaseMgr::transactionCount(const QString& account) const
{
return (m_sql->transactionCount(account));
}
const QMap<QString, unsigned long> MyMoneyDatabaseMgr::transactionCountMap() const
{
QMap<QString, unsigned long> retval;
QHash<QString, unsigned long> hash = m_sql->transactionCountMap();
for (QHash<QString, unsigned long>::ConstIterator i = hash.constBegin();
i != hash.constEnd(); ++i) {
retval[i.key()] = i.value();
}
return retval;
}
const QList<MyMoneyTransaction> MyMoneyDatabaseMgr::transactionList(MyMoneyTransactionFilter& filter) const
{
QList<MyMoneyTransaction> list;
transactionList(list, filter);
return list;
}
void MyMoneyDatabaseMgr::transactionList(QList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const
{
list.clear();
try {
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
list = m_sql->fetchTransactions(filter).values();
}
} catch (const MyMoneyException &) {
throw;
}
}
void MyMoneyDatabaseMgr::transactionList(QList<QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const
{
list.clear();
MyMoneyMap<QString, MyMoneyTransaction> transactionList;
try {
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
transactionList = m_sql->fetchTransactions(filter);
}
} catch (const MyMoneyException &) {
throw;
}
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
QMap<QString, MyMoneyTransaction>::ConstIterator txEnd = transactionList.end();
for (it_t = transactionList.begin(); it_t != txEnd; ++it_t) {
if (filter.match(*it_t)) {
foreach (const MyMoneySplit& it_s, filter.matchingSplits()) {
list.append(qMakePair(*it_t, it_s));
}
}
}
}
void MyMoneyDatabaseMgr::removeAccount(const MyMoneyAccount& account)
{
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<QString, MyMoneyAccount> accountList = m_sql->fetchAccounts(accountIdList, true);
QMap<QString, MyMoneyAccount>::ConstIterator it_a;
QMap<QString, MyMoneyAccount>::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());
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());
m_sql->removeAccount(account);
}
commitTransaction();
}
void MyMoneyDatabaseMgr::removeInstitution(const MyMoneyInstitution& institution)
{
QMap<QString, MyMoneyInstitution> institutionList = m_sql->fetchInstitutions(QStringList(institution.id()));
QMap<QString, MyMoneyInstitution>::ConstIterator it_i;
it_i = institutionList.constFind(institution.id());
if (it_i != institutionList.constEnd()) {
// mark file as changed
m_sql->removeInstitution(institution);
} else
throw MYMONEYEXCEPTION("invalid institution");
}
const MyMoneyTransaction MyMoneyDatabaseMgr::transaction(const QString& id) const
{
// 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 <QString, MyMoneyTransaction> transactionList = 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();
}
const MyMoneyMoney MyMoneyDatabaseMgr::balance(const QString& id, const QDate& date) const
{
QStringList idList;
idList.append(id);
QMap<QString, MyMoneyMoney> tempMap = m_sql->fetchBalance(idList, date);
QMap<QString, MyMoneyMoney>::ConstIterator returnValue = tempMap.constFind(id);
if (returnValue != tempMap.constEnd()) {
return returnValue.value();
}
//DEBUG
QDate date_(date);
//if (date_ == QDate()) date_ = QDate::currentDate();
// END DEBUG
MyMoneyMoney result;
MyMoneyAccount acc;
QMap<QString, MyMoneyAccount> accountList = m_sql->fetchAccounts(/*QString(id)*/);
//QMap<QString, MyMoneyAccount>::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() != MyMoneyAccount::Stock) {
+// if(!date_.isValid() && MyMoneyFile::instance()->account(id).accountType() != eMyMoney::Account::Stock) {
// if(accountList.find(id) != accountList.end())
// return accountList[id].balance();
// return MyMoneyMoney(0);
// }
if (/*m_balanceCache[id].valid == false || date != m_balanceCacheDate) || */ m_sql) {
QMap<QString, MyMoneyMoney> balances;
QMap<QString, MyMoneyMoney>::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<MyMoneyTransaction> 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::ActionSplitShares) {
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<QString, MyMoneyAccount>::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;
}
const MyMoneyTransaction MyMoneyDatabaseMgr::transaction(const QString& account, const int idx) const
{
/* removed with MyMoneyAccount::Transaction
QMap<QString, MyMoneyAccount>::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<MyMoneyTransaction> list;
//MyMoneyAccount acc = m_accountList[account];
MyMoneyAccount acc = m_sql->fetchAccounts(QStringList(account))[account];
MyMoneyTransactionFilter filter;
- if (acc.accountGroup() == MyMoneyAccount::Income
- || acc.accountGroup() == MyMoneyAccount::Expense)
+ if (acc.accountGroup() == eMyMoney::Account::Income
+ || acc.accountGroup() == eMyMoney::Account::Expense)
filter.addCategory(account);
else
filter.addAccount(account);
transactionList(list, filter);
if (idx < 0 || idx >= static_cast<int>(list.count()))
throw MYMONEYEXCEPTION("Unknown idx for transaction");
return transaction(list[idx].id());
}
unsigned int MyMoneyDatabaseMgr::institutionCount() const
{
return m_sql->getRecCount("kmmInstitutions");
}
void MyMoneyDatabaseMgr::accountList(QList<MyMoneyAccount>& list) const
{
QMap <QString, MyMoneyAccount> accountList;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
accountList = m_sql->fetchAccounts();
}
QMap<QString, MyMoneyAccount>::ConstIterator it;
QMap<QString, MyMoneyAccount>::ConstIterator accEnd = accountList.constEnd();
for (it = accountList.constBegin(); it != accEnd; ++it) {
if (!isStandardAccount((*it).id())) {
list.append(*it);
}
}
}
const MyMoneyAccount MyMoneyDatabaseMgr::liability() const
{
return MyMoneyFile::instance()->account(STD_ACC_LIABILITY);
}
const MyMoneyAccount MyMoneyDatabaseMgr::asset() const
{
return MyMoneyFile::instance()->account(STD_ACC_ASSET);
}
const MyMoneyAccount MyMoneyDatabaseMgr::expense() const
{
return MyMoneyFile::instance()->account(STD_ACC_EXPENSE);
}
const MyMoneyAccount MyMoneyDatabaseMgr::income() const
{
return MyMoneyFile::instance()->account(STD_ACC_INCOME);
}
const MyMoneyAccount MyMoneyDatabaseMgr::equity() const
{
return MyMoneyFile::instance()->account(STD_ACC_EQUITY);
}
void MyMoneyDatabaseMgr::addSecurity(MyMoneySecurity& security)
{
// create the account
try {
startTransaction();
MyMoneySecurity newSecurity(nextSecurityID(), security);
m_sql->addSecurity(newSecurity);
security = newSecurity;
commitTransaction();
} catch (...) {
rollbackTransaction();
throw;
}
}
void MyMoneyDatabaseMgr::modifySecurity(const MyMoneySecurity& security)
{
QMap<QString, MyMoneySecurity> securitiesList = m_sql->fetchSecurities(QStringList(security.id()), true);
QMap<QString, MyMoneySecurity>::ConstIterator it;
it = securitiesList.constFind(security.id());
if (it == securitiesList.constEnd()) {
QString msg = "Unknown security '";
msg += security.id() + "' during modifySecurity()";
throw MYMONEYEXCEPTION(msg);
}
m_sql->modifySecurity(security);
}
void MyMoneyDatabaseMgr::removeSecurity(const MyMoneySecurity& security)
{
QMap<QString, MyMoneySecurity> securitiesList = m_sql->fetchSecurities(QStringList(security.id()));
QMap<QString, MyMoneySecurity>::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);
}
m_sql->removeSecurity(security);
}
const MyMoneySecurity MyMoneyDatabaseMgr::security(const QString& id) const
{
QMap<QString, MyMoneySecurity> securitiesList = m_sql->fetchSecurities(QStringList(id));
QMap<QString, MyMoneySecurity>::ConstIterator it = securitiesList.constFind(id);
if (it != securitiesList.constEnd()) {
return it.value();
}
return MyMoneySecurity();
}
const QList<MyMoneySecurity> MyMoneyDatabaseMgr::securityList() const
{
return m_sql->fetchSecurities().values();
}
void MyMoneyDatabaseMgr::addPrice(const MyMoneyPrice& price)
{
MyMoneyPriceEntries::ConstIterator it;
MyMoneyPriceList priceList = 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;
}
m_sql->addPrice(price);
}
void MyMoneyDatabaseMgr::removePrice(const MyMoneyPrice& price)
{
m_sql->removePrice(price);
}
MyMoneyPrice MyMoneyDatabaseMgr::price(const QString& fromId, const QString& toId, const QDate& _date, const bool exactDate) const
{
return m_sql->fetchSinglePrice(fromId, toId, _date, exactDate);
}
const MyMoneyPriceList MyMoneyDatabaseMgr::priceList() const
{
return m_sql->fetchPrices();
}
void MyMoneyDatabaseMgr::addSchedule(MyMoneySchedule& sched)
{
// 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 (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
try {
startTransaction();
sched = MyMoneySchedule(nextScheduleID(), sched);
m_sql->addSchedule(sched);
commitTransaction();
} catch (...) {
rollbackTransaction();
throw;
}
}
}
void MyMoneyDatabaseMgr::modifySchedule(const MyMoneySchedule& sched)
{
QMap<QString, MyMoneySchedule> scheduleList = m_sql->fetchSchedules(QStringList(sched.id()));
QMap<QString, MyMoneySchedule>::ConstIterator it;
it = scheduleList.constFind(sched.id());
if (it == scheduleList.constEnd()) {
QString msg = "Unknown schedule '" + sched.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_sql->modifySchedule(sched);
}
void MyMoneyDatabaseMgr::removeSchedule(const MyMoneySchedule& sched)
{
QMap<QString, MyMoneySchedule> scheduleList = m_sql->fetchSchedules(QStringList(sched.id()));
QMap<QString, MyMoneySchedule>::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
m_sql->removeSchedule(sched);
}
const MyMoneySchedule MyMoneyDatabaseMgr::schedule(const QString& id) const
{
QMap<QString, MyMoneySchedule> scheduleList = m_sql->fetchSchedules(QStringList(id));
QMap<QString, MyMoneySchedule>::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);
}
const QList<MyMoneySchedule> MyMoneyDatabaseMgr::scheduleList(const QString& accountId,
- const MyMoneySchedule::typeE type,
- const MyMoneySchedule::occurrenceE occurrence,
- const MyMoneySchedule::paymentTypeE paymentType,
+ 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
{
QMap<QString, MyMoneySchedule> scheduleList;
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
scheduleList = m_sql->fetchSchedules();
}
QMap<QString, MyMoneySchedule>::ConstIterator pos;
QList<MyMoneySchedule> list;
// qDebug("scheduleList()");
for (pos = scheduleList.constBegin(); pos != scheduleList.constEnd(); ++pos) {
// qDebug(" '%s'", (*pos).id().data());
- if (type != MyMoneySchedule::TYPE_ANY) {
+ if (type != eMyMoney::Schedule::Type::Any) {
if (type != (*pos).type()) {
continue;
}
}
- if (occurrence != MyMoneySchedule::OCCUR_ANY) {
+ if (occurrence != eMyMoney::Schedule::Occurrence::Any) {
if (occurrence != (*pos).occurrence()) {
continue;
}
}
- if (paymentType != MyMoneySchedule::STYPE_ANY) {
+ if (paymentType != eMyMoney::Schedule::PaymentType::Any) {
if (paymentType != (*pos).paymentType()) {
continue;
}
}
if (!accountId.isEmpty()) {
MyMoneyTransaction t = (*pos).transaction();
QList<MyMoneySplit>::ConstIterator it;
QList<MyMoneySplit> 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;
}
const QList<MyMoneySchedule> MyMoneyDatabaseMgr::scheduleListEx(int scheduleTypes,
int scheduleOcurrences,
int schedulePaymentTypes,
QDate startDate,
const QStringList& accounts) const
{
// qDebug("scheduleListEx");
QMap<QString, MyMoneySchedule> scheduleList = m_sql->fetchSchedules();
QMap<QString, MyMoneySchedule>::ConstIterator pos;
QList<MyMoneySchedule> list;
if (!startDate.isValid())
return list;
for (pos = scheduleList.constBegin(); pos != scheduleList.constEnd(); ++pos) {
- if (scheduleTypes && !(scheduleTypes & (*pos).type()))
+ if (scheduleTypes && !(scheduleTypes & (int)(*pos).type()))
continue;
- if (scheduleOcurrences && !(scheduleOcurrences & (*pos).occurrence()))
+ if (scheduleOcurrences && !(scheduleOcurrences & (int)(*pos).occurrence()))
continue;
- if (schedulePaymentTypes && !(schedulePaymentTypes & (*pos).paymentType()))
+ 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)
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
QMap<QString, MyMoneySecurity> currencyList = m_sql->fetchCurrencies(QStringList(currency.id()));
QMap<QString, MyMoneySecurity>::ConstIterator it;
it = currencyList.constFind(currency.id());
if (it != currencyList.constEnd()) {
throw MYMONEYEXCEPTION(i18n("Cannot add currency with existing id %1", currency.id()));
}
m_sql->addCurrency(currency);
}
}
void MyMoneyDatabaseMgr::modifyCurrency(const MyMoneySecurity& currency)
{
QMap<QString, MyMoneySecurity> currencyList = m_sql->fetchCurrencies(QStringList(currency.id()));
QMap<QString, MyMoneySecurity>::ConstIterator it;
it = currencyList.constFind(currency.id());
if (it == currencyList.constEnd()) {
throw MYMONEYEXCEPTION(i18n("Cannot modify currency with unknown id %1", currency.id()));
}
m_sql->modifyCurrency(currency);
}
void MyMoneyDatabaseMgr::removeCurrency(const MyMoneySecurity& currency)
{
QMap<QString, MyMoneySecurity> currencyList = m_sql->fetchCurrencies(QStringList(currency.id()));
QMap<QString, MyMoneySecurity>::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()));
}
m_sql->removeCurrency(currency);
}
const MyMoneySecurity MyMoneyDatabaseMgr::currency(const QString& id) const
{
if (id.isEmpty()) {
}
QMap<QString, MyMoneySecurity> currencyList = m_sql->fetchCurrencies(QStringList(id));
QMap<QString, MyMoneySecurity>::ConstIterator it;
it = currencyList.constFind(id);
if (it == currencyList.constEnd()) {
throw MYMONEYEXCEPTION(i18n("Cannot retrieve currency with unknown id '%1'", id));
}
return *it;
}
const QList<MyMoneySecurity> MyMoneyDatabaseMgr::currencyList() const
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
return m_sql->fetchCurrencies().values();
} else {
return QList<MyMoneySecurity> ();
}
}
const QList<MyMoneyReport> MyMoneyDatabaseMgr::reportList() const
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
return m_sql->fetchReports().values();
} else {
return QList<MyMoneyReport> ();
}
}
void MyMoneyDatabaseMgr::addReport(MyMoneyReport& report)
{
if (!report.id().isEmpty())
throw MYMONEYEXCEPTION("transaction already contains an id");
m_sql->addReport(MyMoneyReport(nextReportID(), report));
}
void MyMoneyDatabaseMgr::modifyReport(const MyMoneyReport& report)
{
QMap<QString, MyMoneyReport> reportList = m_sql->fetchReports(QStringList(report.id()));
QMap<QString, MyMoneyReport>::ConstIterator it;
it = reportList.constFind(report.id());
if (it == reportList.constEnd()) {
QString msg = "Unknown report '" + report.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_sql->modifyReport(report);
}
unsigned MyMoneyDatabaseMgr::countReports() const
{
return m_sql->getRecCount("kmmReports");
}
const MyMoneyReport MyMoneyDatabaseMgr::report(const QString& id) const
{
return m_sql->fetchReports(QStringList(id))[id];
}
void MyMoneyDatabaseMgr::removeReport(const MyMoneyReport& report)
{
QMap<QString, MyMoneyReport> reportList = m_sql->fetchReports(QStringList(report.id()));
QMap<QString, MyMoneyReport>::ConstIterator it;
it = reportList.constFind(report.id());
if (it == reportList.constEnd()) {
QString msg = "Unknown report '" + report.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_sql->removeReport(report);
}
const QList<MyMoneyBudget> MyMoneyDatabaseMgr::budgetList() const
{
return m_sql->fetchBudgets().values();
}
void MyMoneyDatabaseMgr::addBudget(MyMoneyBudget& budget)
{
MyMoneyBudget newBudget(nextBudgetID(), budget);
m_sql->addBudget(newBudget);
}
const MyMoneyBudget MyMoneyDatabaseMgr::budgetByName(const QString& budget) const
{
QMap<QString, MyMoneyBudget> budgets = m_sql->fetchBudgets();
QMap<QString, MyMoneyBudget>::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)
{
//QMap<QString, MyMoneyBudget>::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 (m_sql->fetchBudgets(QStringList(budget.id()), true).empty()) {
QString msg = "Unknown budget '" + budget.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_sql->modifyBudget(budget);
commitTransaction();
}
unsigned MyMoneyDatabaseMgr::countBudgets() const
{
return m_sql->getRecCount("kmmBudgetConfig");
}
MyMoneyBudget MyMoneyDatabaseMgr::budget(const QString& id) const
{
return m_sql->fetchBudgets(QStringList(id))[id];
}
void MyMoneyDatabaseMgr::removeBudget(const MyMoneyBudget& budget)
{
// QMap<QString, MyMoneyBudget>::ConstIterator it;
//
// it = m_budgetList.find(budget.id());
// if(it == m_budgetList.end()) {
// QString msg = "Unknown budget '" + budget.id() + '\'';
// throw MYMONEYEXCEPTION(msg);
// }
//
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_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 <QString, MyMoneyTransaction> 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 (m_sql->isReferencedByTransaction(id))
return true;
}
}
if (!skipCheck.testBit((int)Reference::Account)) {
QList<MyMoneyAccount> accountList;
MyMoneyFile::instance()->accountList(accountList);
foreach (const auto it, accountList)
if (it.hasReferenceTo(id))
return true;
}
if (!skipCheck.testBit((int)Reference::Institution)) {
QList<MyMoneyInstitution> 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, m_sql->fetchReports())
if (it.hasReferenceTo(id))
return true;
}
if (!skipCheck.testBit((int)Reference::Budget)) {
foreach (const auto it, m_sql->fetchBudgets())
if (it.hasReferenceTo(id))
return true;
}
if (!skipCheck.testBit((int)Reference::Schedule)) {
foreach (const auto it, 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 = 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 = 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()
{
if (m_sql) {
m_sql->close(true);
m_sql = 0;
}
}
void MyMoneyDatabaseMgr::startTransaction()
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
m_sql->startCommitUnit("databasetransaction");
}
}
bool MyMoneyDatabaseMgr::commitTransaction()
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
return m_sql->endCommitUnit("databasetransaction");
}
return false;
}
void MyMoneyDatabaseMgr::rollbackTransaction()
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
m_sql->cancelCommitUnit("databasetransaction");
}
}
void MyMoneyDatabaseMgr::setCreationDate(const QDate& val)
{
m_creationDate = val;
}
QExplicitlySharedDataPointer <MyMoneyStorageSql> MyMoneyDatabaseMgr::connectToDatabase(const QUrl &url)
{
m_sql = new MyMoneyStorageSql(this, url);
return m_sql;
}
void MyMoneyDatabaseMgr::fillStorage()
{
m_sql->fillStorage();
}
void MyMoneyDatabaseMgr::setLastModificationDate(const QDate& val)
{
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<QString, MyMoneyAccount>& /*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<QString, MyMoneyTransaction>& /*map*/)
{
// m_transactionList = map;
//FIXME: update the database.
// // now fill the key map
// QMap<QString, QString> keys;
// QMap<QString, MyMoneyTransaction>::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<QString, MyMoneyInstitution>& /*map*/)
{
// m_institutionList = map;
//FIXME: update the database.
// // now fill the key map
// QMap<QString, QString> keys;
// QMap<QString, MyMoneyTransaction>::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<QString, MyMoneyPayee>& /*map*/)
{
// m_payeeList = map;
}
void MyMoneyDatabaseMgr::loadTags(const QMap<QString, MyMoneyTag>& /*map*/)
{
// m_tagList = map;
}
void MyMoneyDatabaseMgr::loadSchedules(const QMap<QString, MyMoneySchedule>& /*map*/)
{
// m_scheduleList = map;
}
void MyMoneyDatabaseMgr::loadSecurities(const QMap<QString, MyMoneySecurity>& /*map*/)
{
// m_securitiesList = map;
}
void MyMoneyDatabaseMgr::loadCurrencies(const QMap<QString, MyMoneySecurity>& /*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<QString, MyMoneyReport>& /*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<QString, MyMoneyBudget>& /*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);
}
unsigned long MyMoneyDatabaseMgr::accountId() const
{
return m_sql->getNextAccountId() - 1;
}
unsigned long MyMoneyDatabaseMgr::transactionId() const
{
return m_sql->getNextTransactionId() - 1;
}
unsigned long MyMoneyDatabaseMgr::payeeId() const
{
return m_sql->getNextPayeeId() - 1;
}
unsigned long MyMoneyDatabaseMgr::tagId() const
{
return m_sql->getNextTagId() - 1;
}
unsigned long MyMoneyDatabaseMgr::institutionId() const
{
return m_sql->getNextInstitutionId() - 1;
}
unsigned long MyMoneyDatabaseMgr::scheduleId() const
{
return m_sql->getNextScheduleId() - 1;
}
unsigned long MyMoneyDatabaseMgr::securityId() const
{
return m_sql->getNextSecurityId() - 1;
}
unsigned long MyMoneyDatabaseMgr::reportId() const
{
return m_sql->getNextReportId() - 1;
}
unsigned long MyMoneyDatabaseMgr::budgetId() const
{
return m_sql->getNextBudgetId() - 1;
}
long unsigned int MyMoneyDatabaseMgr::onlineJobId() const
{
return m_sql->getNextOnlineJobId() - 1;
}
long unsigned int MyMoneyDatabaseMgr::payeeIdentifierId() const
{
return m_sql->getNextPayeeIdentifierId() - 1;
}
void MyMoneyDatabaseMgr::loadAccountId(const unsigned long id)
{
m_sql->loadAccountId(id);
}
void MyMoneyDatabaseMgr::loadTransactionId(const unsigned long id)
{
m_sql->loadTransactionId(id);
}
void MyMoneyDatabaseMgr::loadPayeeId(const unsigned long id)
{
m_sql->loadPayeeId(id);
}
void MyMoneyDatabaseMgr::loadTagId(const unsigned long id)
{
m_sql->loadTagId(id);
}
void MyMoneyDatabaseMgr::loadInstitutionId(const unsigned long id)
{
m_sql->loadInstitutionId(id);
}
void MyMoneyDatabaseMgr::loadScheduleId(const unsigned long id)
{
m_sql->loadScheduleId(id);
}
void MyMoneyDatabaseMgr::loadSecurityId(const unsigned long id)
{
m_sql->loadSecurityId(id);
}
void MyMoneyDatabaseMgr::loadReportId(const unsigned long id)
{
m_sql->loadReportId(id);
}
void MyMoneyDatabaseMgr::loadBudgetId(const unsigned long id)
{
m_sql->loadBudgetId(id);
}
void MyMoneyDatabaseMgr::loadOnlineJobId(const long unsigned int id)
{
m_sql->loadOnlineJobId(id);
}
void MyMoneyDatabaseMgr::loadPayeeIdentifierId(const long unsigned int id)
{
m_sql->loadPayeeIdentifierId(id);
}
void MyMoneyDatabaseMgr::loadCostCenterId(const long unsigned int id)
{
m_sql->loadAccountId(id);
}
void MyMoneyDatabaseMgr::rebuildAccountBalances()
{
startTransaction();
QMap<QString, MyMoneyAccount> accountMap = m_sql->fetchAccounts(QStringList(), true);
QMap<QString, MyMoneyMoney> balanceMap = m_sql->fetchBalance(accountMap.keys(), QDate());
for (QMap<QString, MyMoneyMoney>::const_iterator it_b = balanceMap.constBegin();
it_b != balanceMap.constEnd(); ++it_b) {
accountMap[it_b.key()].setBalance(it_b.value());
}
QList<MyMoneyAccount> aList;
for (QMap<QString, MyMoneyAccount>::const_iterator it_a = accountMap.constBegin();
it_a != accountMap.constEnd(); ++it_a) {
aList << it_a.value();
}
m_sql->modifyAccountList(aList);
commitTransaction();
}
void MyMoneyDatabaseMgr::removeReferences(const QString& id)
{
QMap<QString, MyMoneyReport>::const_iterator it_r;
QMap<QString, MyMoneyBudget>::const_iterator it_b;
// remove from reports
QMap<QString, MyMoneyReport> 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<QString, MyMoneyBudget> 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);
}
}
const QList< MyMoneyCostCenter > MyMoneyDatabaseMgr::costCenterList() const
{
if (m_sql) {
if (! m_sql->isOpen())((QSqlDatabase*)(m_sql.data()))->open();
return m_sql->fetchCostCenters().values();
}
return QList<MyMoneyCostCenter> ();
}
void MyMoneyDatabaseMgr::loadCostCenters(const QMap< QString, MyMoneyCostCenter >& costCenters)
{
Q_UNUSED(costCenters);
}
long unsigned int MyMoneyDatabaseMgr::costCenterId() const
{
return m_sql->getNextCostCenterId() - 1;
}
const MyMoneyCostCenter MyMoneyDatabaseMgr::costCenter(const QString& id) const
{
QMap<QString, MyMoneyCostCenter>::ConstIterator it;
QMap<QString, MyMoneyCostCenter> costCenterList = 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
index d6bf37668..8483875c7 100644
--- a/kmymoney/mymoney/storage/mymoneydatabasemgr.h
+++ b/kmymoney/mymoney/storage/mymoneydatabasemgr.h
@@ -1,1143 +1,1143 @@
/***************************************************************************
mymoneydatabasemgr.h - description
-------------------
begin : June 5 2007
copyright : (C) 2007 by Fernando Vilas
email : Fernando Vilas <fvilas@iname.com>
2017 by Łukasz Wojniłowicz <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 MYMONEYDATABASEMGR_H
#define MYMONEYDATABASEMGR_H
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "imymoneyserialize.h"
#include "imymoneystorage.h"
#include "mymoneystoragesql.h"
/**
* 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 MyMoneyDatabaseMgr : public IMyMoneyStorage, public IMyMoneySerialize,
public MyMoneyKeyValueContainer
{
KMM_MYMONEY_UNIT_TESTABLE
public:
MyMoneyDatabaseMgr();
~MyMoneyDatabaseMgr();
// general get functions
virtual const MyMoneyPayee& user() const;
virtual const QDate creationDate() const;
virtual const QDate lastModificationDate() const;
virtual unsigned int currentFixVersion() const;
virtual unsigned int fileFixVersion() const;
// general set functions
virtual void setUser(const MyMoneyPayee& user);
virtual void setFileFixVersion(const unsigned int v);
// methods provided by MyMoneyKeyValueContainer
virtual void setValue(const QString& key, const QString& value);
virtual const QString value(const QString& key) const;
virtual void deletePair(const QString& key);
/**
* 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 MyMoneyDatabaseMgr const * duplicate();
/**
* 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);
/**
* 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);
/**
* 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);
/**
* 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 const 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
*/
virtual const 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
*/
virtual 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
*/
virtual void removePayee(const MyMoneyPayee& payee);
/**
* This method returns a list of the payees
* inside a MyMoneyStorage object
*
* @return QList<MyMoneyPayee> containing the payee information
*/
virtual const QList<MyMoneyPayee> 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
*/
virtual 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
*/
virtual const 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
*/
virtual const 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
*/
virtual 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
*/
virtual void removeTag(const MyMoneyTag& tag);
/**
* This method returns a list of the tags
* inside a MyMoneyStorage object
*
* @return QList<MyMoneyTag> containing the tag information
*/
virtual const QList<MyMoneyTag> tagList() const;
/** @todo implement all onlineJob related functions @{ */
void modifyOnlineJob(const onlineJob& job);
void addOnlineJob(onlineJob& job);
const onlineJob getOnlineJob(const QString &jobId) const;
const QList<onlineJob> onlineJobList() const;
void removeOnlineJob(const onlineJob&);
/** @} */
/**
* 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 const 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.
*
* 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;
/**
* 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);
/**
* 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);
/**
* 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, const bool skipAccountUpdate = false);
/**
* 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;
/**
* 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 const 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
*/
virtual const 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
*/
virtual const 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 (for a database, always false).
*/
virtual 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.
*
* Since the database is synchronized with the application, this method
* is a no-op.
*/
virtual void setDirty();
/**
* 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 unsigned int accountCount() const;
/**
* This method returns a list of the institutions
* inside a MyMoneyStorage object
*
* @return QList<MyMoneyInstitution> containing the
* institution information
*/
virtual const QList<MyMoneyInstitution> institutionList() const;
/**
* 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, const 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
*/
virtual 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
*/
virtual void modifyTransaction(const MyMoneyTransaction& transaction);
/**
* 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);
/**
* 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);
/**
* 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 unsigned int transactionCount(const QString& account = QString()) 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
*/
virtual const QMap<QString, unsigned long> transactionCountMap() 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<MyMoneyTransaction>
*/
virtual const QList<MyMoneyTransaction> transactionList(MyMoneyTransactionFilter& filter) const;
/**
* 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
*/
virtual void transactionList(QList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const;
/**
* 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
*/
virtual void transactionList(QList<QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) 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.
*/
virtual 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.
*
* An exception will be thrown upon error conditions.
*
* @param institution institution to be deleted.
*/
virtual void removeInstitution(const MyMoneyInstitution& institution);
/**
* 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
*/
virtual const 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 MyMoneyTransaction object
*/
virtual const MyMoneyTransaction transaction(const QString& account, const int idx) const;
/**
* This method returns the number of institutions currently known to file
* in the range 0..MAXUINT
*
* @return number of institutions known to file
*/
virtual unsigned int institutionCount() 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
*/
virtual void accountList(QList<MyMoneyAccount>& list) const;
/**
* This method is used to return the standard liability account
* @return MyMoneyAccount liability account(group)
*/
virtual const MyMoneyAccount liability() const;
/**
* This method is used to return the standard asset account
* @return MyMoneyAccount asset account(group)
*/
virtual const MyMoneyAccount asset() const;
/**
* This method is used to return the standard expense account
* @return MyMoneyAccount expense account(group)
*/
virtual const MyMoneyAccount expense() const;
/**
* This method is used to return the standard income account
* @return MyMoneyAccount income account(group)
*/
virtual const MyMoneyAccount income() const;
/**
* This method is used to return the standard equity account
* @return MyMoneyAccount equity account(group)
*/
virtual const MyMoneyAccount equity() 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
*/
virtual void addSecurity(MyMoneySecurity& security);
/**
* 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);
/**
* 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);
/**
* 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 const MyMoneySecurity security(const QString& id) const;
/**
* This method returns a list of the security objects
* inside a MyMoneyStorage object
*
* @return QList<MyMoneySecurity> containing objects
*/
virtual const QList<MyMoneySecurity> securityList() const;
virtual void addPrice(const MyMoneyPrice& price);
virtual void removePrice(const MyMoneyPrice& price);
virtual MyMoneyPrice price(const QString& fromId, const QString& toId, const QDate& _date, const bool exactDate) const;
/**
* This method returns a list of all prices.
*
* @return MyMoneyPriceList of all MyMoneyPrice objects.
*/
virtual const MyMoneyPriceList priceList() const;
/**
* 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);
/**
* 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);
/**
* 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);
/**
* 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 const 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
* 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 MyMoneySchedule::typeE for details.
- * Default is MyMoneySchedule::TYPE_ANY
+ * 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 MyMoneySchedule::occurrenceE for details.
- * Default is MyMoneySchedule::OCCUR_ANY
+ * See eMyMoney::Schedule::Occurence for details.
+ * Default is eMyMoney::Schedule::Occurrence::Any
* @param paymentType only schedules of payment method @p paymentType
* are searched for.
- * See MyMoneySchedule::paymentTypeE for details.
- * Default is MyMoneySchedule::STYPE_ANY
+ * 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<MyMoneySchedule> list of schedule objects.
*/
virtual const QList<MyMoneySchedule> scheduleList(const QString& accountId = QString(),
- const MyMoneySchedule::typeE type = MyMoneySchedule::TYPE_ANY,
- const MyMoneySchedule::occurrenceE occurrence = MyMoneySchedule::OCCUR_ANY,
- const MyMoneySchedule::paymentTypeE paymentType = MyMoneySchedule::STYPE_ANY,
+ const eMyMoney::Schedule::Type type = eMyMoney::Schedule::Type::Any,
+ const eMyMoney::Schedule::Occurrence occurrence = eMyMoney::Schedule::Occurrence::Any,
+ const eMyMoney::Schedule::PaymentType paymentType = eMyMoney::Schedule::PaymentType::Any,
const QDate& startDate = QDate(),
const QDate& endDate = QDate(),
const bool overdue = false) const;
virtual const QList<MyMoneySchedule> scheduleListEx(int scheduleTypes,
int scheduleOcurrences,
int schedulePaymentTypes,
QDate startDate,
const QStringList& accounts = QStringList()) 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
*/
virtual 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 MyMoneyCurrency object
*/
virtual 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
*/
virtual void removeCurrency(const MyMoneySecurity& currency);
/**
* 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 const 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 MyMoneySecurity objects representing a currency.
*/
virtual const QList<MyMoneySecurity> currencyList() 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.
*/
virtual const QList<MyMoneyReport> 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
*/
virtual void addReport(MyMoneyReport& report);
/**
* 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);
/**
* This method returns the number of reports currently known to file
* in the range 0..MAXUINT
*
* @return number of reports known to file
*/
virtual 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
*/
virtual const 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
*/
virtual 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.
*/
virtual const QList<MyMoneyBudget> 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
*/
virtual 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 object of budget
*/
virtual const MyMoneyBudget budgetByName(const QString& budget) const;
/**
* 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);
/**
* This method returns the number of budgets currently known to file
* in the range 0..MAXUINT
*
* @return number of budgets known to file
*/
virtual 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
*/
virtual 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
*/
virtual void removeBudget(const MyMoneyBudget& budget);
/**
* This method returns a list of all cost center objects
*/
virtual const QList<MyMoneyCostCenter> costCenterList() const;
/**
* @brief Return cost center object by id
*/
const MyMoneyCostCenter costCenter(const QString& id) const;
/**
* Clear all internal caches (used internally for performance measurements)
*/
virtual 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
*/
virtual bool isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const override;
/**
* This method is provided to allow closing of the database before logoff
*/
virtual void close();
/**
* These methods have to be provided to allow transaction safe data handling.
*/
virtual void startTransaction();
virtual bool commitTransaction();
virtual void rollbackTransaction();
// general set functions
virtual void setCreationDate(const QDate& val);
/**
* This method is used to get a SQL reader for subsequent database access
*/
virtual QExplicitlySharedDataPointer <MyMoneyStorageSql> connectToDatabase
(const QUrl &url);
/**
* 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();
/**
* 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);
/**
* This method returns whether a given transaction is already in memory, to avoid
* reloading it from the database
*/
virtual bool isDuplicateTransaction(const QString&) const;
virtual void loadAccounts(const QMap<QString, MyMoneyAccount>& map);
virtual void loadTransactions(const QMap<QString, MyMoneyTransaction>& map);
virtual void loadInstitutions(const QMap<QString, MyMoneyInstitution>& map);
virtual void loadPayees(const QMap<QString, MyMoneyPayee>& map);
virtual void loadTags(const QMap<QString, MyMoneyTag>& map);
virtual void loadSchedules(const QMap<QString, MyMoneySchedule>& map);
virtual void loadSecurities(const QMap<QString, MyMoneySecurity>& map);
virtual void loadCurrencies(const QMap<QString, MyMoneySecurity>& map);
virtual void loadReports(const QMap<QString, MyMoneyReport>& reports);
virtual void loadBudgets(const QMap<QString, MyMoneyBudget>& budgets);
virtual void loadPrices(const MyMoneyPriceList& list);
virtual void loadOnlineJobs(const QMap<QString, onlineJob>& onlineJobs);
virtual void loadCostCenters(const QMap<QString, MyMoneyCostCenter>& costCenters);
//virtual void loadPayeeIdentifier(const QMap<QString, payeeIdentifier>& idents);
virtual unsigned long accountId() const;
virtual unsigned long transactionId() const;
virtual unsigned long payeeId() const;
virtual unsigned long tagId() const;
virtual unsigned long institutionId() const;
virtual unsigned long scheduleId() const;
virtual unsigned long securityId() const;
virtual unsigned long reportId() const;
virtual unsigned long budgetId() const;
virtual unsigned long onlineJobId() const;
virtual unsigned long payeeIdentifierId() const;
virtual unsigned long costCenterId() const;
virtual void loadAccountId(const unsigned long id);
virtual void loadTransactionId(const unsigned long id);
virtual void loadPayeeId(const unsigned long id);
virtual void loadTagId(const unsigned long id);
virtual void loadInstitutionId(const unsigned long id);
virtual void loadScheduleId(const unsigned long id);
virtual void loadSecurityId(const unsigned long id);
virtual void loadReportId(const unsigned long id);
virtual void loadBudgetId(const unsigned long id);
virtual void loadOnlineJobId(const unsigned long id);
virtual void loadPayeeIdentifierId(const unsigned long id);
virtual void loadCostCenterId(const unsigned long id);
/**
* 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<QString, QString> containing all key/value pairs of
* this container.
*/
virtual const QMap<QString, QString> pairs() const;
/**
* 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<QString, QString> 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<QString, QString>& list);
/**
* This method recalculates the balances of all accounts
* based on the transactions stored in the engine.
*/
virtual void rebuildAccountBalances();
private:
/**
* This member variable keeps the creation date of this MyMoneySeqAccessMgr
* 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)
*/
unsigned int m_currentFixVersion;
/**
* This member variable contains the current fix level of the
* presently open data file. (see kmymoneyview.cpp)
*/
unsigned int m_fileFixVersion;
/**
* This member variable keeps the date of the last modification of
* the MyMoneySeqAccessMgr object.
*/
QDate m_lastModificationDate;
/**
* This contains the interface with SQL reader for database access
*/
QExplicitlySharedDataPointer <MyMoneyStorageSql> m_sql;
/**
* This member variable keeps the User information.
* @see setUser()
*/
MyMoneyPayee m_user;
/**
* This method is used to get the next valid ID for a institution
* @return id for a institution
*/
const QString nextInstitutionID();
/**
* This method is used to get the next valid ID for an account
* @return id for an account
*/
const QString nextAccountID();
/**
* This method is used to get the next valid ID for a transaction
* @return id for a transaction
*/
const QString nextTransactionID();
/**
* This method is used to get the next valid ID for a payee
* @return id for a payee
*/
const QString nextPayeeID();
/**
* This method is used to get the next valid ID for a tag
* @return id for a tag
*/
const QString nextTagID();
/**
* This method is used to get the next valid ID for a scheduled transaction
* @return id for a scheduled transaction
*/
const QString nextScheduleID();
/**
* This method is used to get the next valid ID for an security object.
* @return id for an security object
*/
const QString nextSecurityID();
const QString nextReportID();
/** @brief get next valid id for an onlineJob */
const QString nextOnlineJobID();
/** @brief get next valid id for payeeIdentifier */
const QString nextPayeeIdentifierID();
/** @brief get next valid id for a cost center */
const QString nextCostCenterID();
/**
* This method is used to get the next valid ID for a budget object.
* @return id for an budget object
*/
const QString nextBudgetID();
void removeReferences(const QString& id);
static const int INSTITUTION_ID_SIZE = 6;
static const int ACCOUNT_ID_SIZE = 6;
static const int TRANSACTION_ID_SIZE = 18;
static const int PAYEE_ID_SIZE = 6;
static const int TAG_ID_SIZE = 6;
static const int SCHEDULE_ID_SIZE = 6;
static const int SECURITY_ID_SIZE = 6;
static const int REPORT_ID_SIZE = 6;
static const int BUDGET_ID_SIZE = 6;
static const int ONLINEJOB_ID_SIZE = 8;
static const int PAYEEIDENTIFIER_ID_SIZE = 6;
static 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
static const int CURRENT_FIX_VERSION = 4;
};
#endif
diff --git a/kmymoney/mymoney/storage/mymoneydbdef.cpp b/kmymoney/mymoney/storage/mymoneydbdef.cpp
index d3d806ede..b6c350cc7 100644
--- a/kmymoney/mymoney/storage/mymoneydbdef.cpp
+++ b/kmymoney/mymoney/storage/mymoneydbdef.cpp
@@ -1,771 +1,771 @@
/***************************************************************************
mymoneydbdef.h
-------------------
begin : 20 February 2010
copyright : (C) 2010 by Fernando Vilas
email : tonybloom@users.sourceforge.net
: Fernando Vilas <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 "mymoneydbdef.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QDate>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneydbdriver.h"
#include "mymoneyfile.h"
#include "imymoneystorage.h"
//***************** THE CURRENT VERSION OF THE DATABASE LAYOUT ****************
unsigned int MyMoneyDbDef::m_currentVersion = 11;
// ************************* 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<MyMoneyDbColumn>(new a))
void MyMoneyDbDef::FileInfo()
{
QList< QExplicitlySharedDataPointer<MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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,
false, NOTNULL, 3));
appendField(MyMoneyDbTextColumn("occurenceString"));
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("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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<int>::max(), QString("%1").arg(AlkValue::RoundRound)));
MyMoneyDbTable t("kmmSecurities", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Prices()
{
QList<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<int>::max(), QLatin1String("4")));
MyMoneyDbTable t("kmmCurrencies", fields);
t.buildSQLStrings();
m_tables[t.name()] = t;
}
void MyMoneyDbDef::Reports()
{
QList<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer <MyMoneyDbColumn> > 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<QExplicitlySharedDataPointer<MyMoneyDbColumn> > 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<MyMoneyDbDriver>& 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 paramter starts with the name of another one.
}
qs += "\n\n";
retval += qs;
// Add the strings to create the initial accounts
qs.clear();
QList<MyMoneyAccount> 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(pac->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<MyMoneyDbDriver>& 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<MyMoneyDbDriver>& 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<MyMoneyDbDriver>& driver, const QString& columnName, const MyMoneyDbColumn& newDef) const
{
return driver->modifyColumnString(m_name, columnName, newDef);
}
int MyMoneyDbTable::fieldNumber(const QString& name) const
{
QHash<QString, int>::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<MyMoneyDbDriver>& 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<MyMoneyDbDriver>& 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<MyMoneyDbDriver>& driver) const
{
QString qs = driver->intString(*this);
if (!defaultValue().isEmpty())
qs += QString(" DEFAULT %1").arg(defaultValue());
return qs;
}
const QString MyMoneyDbTextColumn::generateDDL(const QExplicitlySharedDataPointer<MyMoneyDbDriver>& driver) const
{
return driver->textString(*this);
}
const QString MyMoneyDbDatetimeColumn::generateDDL(const QExplicitlySharedDataPointer<MyMoneyDbDriver>& driver) const
{
return driver->timestampString(*this);
}
diff --git a/kmymoney/mymoney/storage/mymoneyseqaccessmgr.cpp b/kmymoney/mymoney/storage/mymoneyseqaccessmgr.cpp
index 8bb3929df..480f71b78 100644
--- a/kmymoney/mymoney/storage/mymoneyseqaccessmgr.cpp
+++ b/kmymoney/mymoney/storage/mymoneyseqaccessmgr.cpp
@@ -1,2097 +1,2097 @@
/***************************************************************************
mymoneyseqaccessmgr.cpp
-------------------
begin : Sun May 5 2002
copyright : (C) 2000-2002 by Michael Edwardes <mte@users.sourceforge.net>
2002 Thomas Baumgart <ipwizard@users.sourceforge.net>
2017 by Łukasz Wojniłowicz <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 "mymoneyseqaccessmgr.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
#include <QBitArray>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneytransactionfilter.h"
#include "mymoneyexception.h"
#include "mymoneystoragesql.h"
#include "storageenums.h"
#define TRY try {
#define CATCH } catch (const MyMoneyException &e) {
#define PASS } catch (const MyMoneyException &e) { throw; }
using namespace eStorage;
MyMoneySeqAccessMgr::MyMoneySeqAccessMgr()
{
m_nextAccountID = 0;
m_nextInstitutionID = 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_user = MyMoneyPayee();
m_dirty = false;
m_creationDate = QDate::currentDate();
// setup standard accounts
MyMoneyAccount acc_l;
- acc_l.setAccountType(MyMoneyAccount::Liability);
+ acc_l.setAccountType(eMyMoney::Account::Liability);
acc_l.setName("Liability");
MyMoneyAccount liability(STD_ACC_LIABILITY, acc_l);
MyMoneyAccount acc_a;
- acc_a.setAccountType(MyMoneyAccount::Asset);
+ acc_a.setAccountType(eMyMoney::Account::Asset);
acc_a.setName("Asset");
MyMoneyAccount asset(STD_ACC_ASSET, acc_a);
MyMoneyAccount acc_e;
- acc_e.setAccountType(MyMoneyAccount::Expense);
+ acc_e.setAccountType(eMyMoney::Account::Expense);
acc_e.setName("Expense");
MyMoneyAccount expense(STD_ACC_EXPENSE, acc_e);
MyMoneyAccount acc_i;
- acc_i.setAccountType(MyMoneyAccount::Income);
+ acc_i.setAccountType(eMyMoney::Account::Income);
acc_i.setName("Income");
MyMoneyAccount income(STD_ACC_INCOME, acc_i);
MyMoneyAccount acc_q;
- acc_q.setAccountType(MyMoneyAccount::Equity);
+ acc_q.setAccountType(eMyMoney::Account::Equity);
acc_q.setName("Equity");
MyMoneyAccount equity(STD_ACC_EQUITY, acc_q);
QMap<QString, MyMoneyAccount> map;
map[STD_ACC_ASSET] = asset;
map[STD_ACC_LIABILITY] = liability;
map[STD_ACC_INCOME] = income;
map[STD_ACC_EXPENSE] = expense;
map[STD_ACC_EQUITY] = equity;
// load account list with initial accounts
m_accountList = map;
// initialize for file fixes (see kmymoneyview.cpp)
m_currentFixVersion = 4;
m_fileFixVersion = 0; // default value if no fix-version in file
m_transactionListFull = false;
}
MyMoneySeqAccessMgr::~MyMoneySeqAccessMgr()
{
}
MyMoneySeqAccessMgr const * MyMoneySeqAccessMgr::duplicate()
{
MyMoneySeqAccessMgr* that = new MyMoneySeqAccessMgr();
*that = *this;
return that;
}
/**
* This method is used to get a SQL reader for subsequent database access
*/
QExplicitlySharedDataPointer <MyMoneyStorageSql> MyMoneySeqAccessMgr::connectToDatabase
(const QUrl& /*url*/)
{
return QExplicitlySharedDataPointer <MyMoneyStorageSql>();
}
bool MyMoneySeqAccessMgr::isStandardAccount(const QString& id) const
{
return id == STD_ACC_LIABILITY
|| id == STD_ACC_ASSET
|| id == STD_ACC_EXPENSE
|| id == STD_ACC_INCOME
|| id == STD_ACC_EQUITY;
}
void MyMoneySeqAccessMgr::setAccountName(const QString& id, const QString& name)
{
if (!isStandardAccount(id))
throw MYMONEYEXCEPTION("Only standard accounts can be modified using setAccountName()");
MyMoneyAccount acc = m_accountList[id];
acc.setName(name);
m_accountList.modify(acc.id(), acc);
}
const MyMoneyAccount MyMoneySeqAccessMgr::account(const QString& id) const
{
// locate the account and if present, return it's data
if (m_accountList.find(id) != m_accountList.end())
return m_accountList[id];
// throw an exception, if it does not exist
QString msg = "Unknown account id '" + id + '\'';
throw MYMONEYEXCEPTION(msg);
}
void MyMoneySeqAccessMgr::accountList(QList<MyMoneyAccount>& list) const
{
QMap<QString, MyMoneyAccount>::ConstIterator it;
for (it = m_accountList.begin(); it != m_accountList.end(); ++it) {
if (!isStandardAccount((*it).id())) {
list.append(*it);
}
}
}
void MyMoneySeqAccessMgr::addAccount(MyMoneyAccount& account)
{
// create the account.
MyMoneyAccount newAccount(nextAccountID(), account);
m_accountList.insert(newAccount.id(), newAccount);
account = newAccount;
}
void MyMoneySeqAccessMgr::addPayee(MyMoneyPayee& payee)
{
// create the payee
MyMoneyPayee newPayee(nextPayeeID(), payee);
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)
{
onlineJob newJob = onlineJob(nextOnlineJobID(), job);
m_onlineJobList.insert(newJob.id(), newJob);
job = newJob;
}
void MyMoneySeqAccessMgr::removeOnlineJob(const onlineJob& job)
{
if (!m_onlineJobList.contains(job.id())) {
throw MYMONEYEXCEPTION("Unknown onlineJob '" + job.id() + "' should be removed.");
}
m_onlineJobList.remove(job.id());
}
void MyMoneySeqAccessMgr::modifyOnlineJob(const onlineJob &job)
{
QMap<QString, onlineJob>::ConstIterator iter = m_onlineJobList.find(job.id());
if (iter == m_onlineJobList.end()) {
throw MYMONEYEXCEPTION("Got unknown onlineJob '" + job.id() + "' for modifying");
}
onlineJob oldJob = iter.value();
m_onlineJobList.modify((*iter).id(), job);
}
const onlineJob MyMoneySeqAccessMgr::getOnlineJob(const QString &id) const
{
if (m_onlineJobList.contains(id)) {
return m_onlineJobList[id];
}
throw MYMONEYEXCEPTION("Unknown online Job '" + id + "'");
}
const MyMoneyPayee MyMoneySeqAccessMgr::payee(const QString& id) const
{
QMap<QString, MyMoneyPayee>::ConstIterator it;
it = m_payeeList.find(id);
if (it == m_payeeList.end())
throw MYMONEYEXCEPTION("Unknown payee '" + id + '\'');
return *it;
}
const MyMoneyPayee MyMoneySeqAccessMgr::payeeByName(const QString& payee) const
{
if (payee.isEmpty())
return MyMoneyPayee::null;
QMap<QString, MyMoneyPayee>::ConstIterator it_p;
for (it_p = m_payeeList.begin(); it_p != m_payeeList.end(); ++it_p) {
if ((*it_p).name() == payee) {
return *it_p;
}
}
throw MYMONEYEXCEPTION("Unknown payee '" + payee + '\'');
}
void MyMoneySeqAccessMgr::modifyPayee(const MyMoneyPayee& payee)
{
QMap<QString, MyMoneyPayee>::ConstIterator it;
it = m_payeeList.find(payee.id());
if (it == m_payeeList.end()) {
QString msg = "Unknown payee '" + payee.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_payeeList.modify((*it).id(), payee);
}
void MyMoneySeqAccessMgr::removePayee(const MyMoneyPayee& payee)
{
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
QMap<QString, MyMoneySchedule>::ConstIterator it_s;
QMap<QString, MyMoneyPayee>::ConstIterator it_p;
it_p = m_payeeList.find(payee.id());
if (it_p == 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 = m_transactionList.begin(); it_t != 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 = m_scheduleList.begin(); it_s != 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
removeReferences(payee.id());
m_payeeList.remove((*it_p).id());
}
const QList<MyMoneyPayee> MyMoneySeqAccessMgr::payeeList() const
{
return m_payeeList.values();
}
void MyMoneySeqAccessMgr::addTag(MyMoneyTag& tag)
{
// create the tag
MyMoneyTag newTag(nextTagID(), tag);
m_tagList.insert(newTag.id(), newTag);
tag = newTag;
}
const MyMoneyTag MyMoneySeqAccessMgr::tag(const QString& id) const
{
QMap<QString, MyMoneyTag>::ConstIterator it;
it = m_tagList.find(id);
if (it == m_tagList.end())
throw MYMONEYEXCEPTION("Unknown tag '" + id + '\'');
return *it;
}
const MyMoneyTag MyMoneySeqAccessMgr::tagByName(const QString& tag) const
{
if (tag.isEmpty())
return MyMoneyTag::null;
QMap<QString, MyMoneyTag>::ConstIterator it_ta;
for (it_ta = m_tagList.begin(); it_ta != m_tagList.end(); ++it_ta) {
if ((*it_ta).name() == tag) {
return *it_ta;
}
}
throw MYMONEYEXCEPTION("Unknown tag '" + tag + '\'');
}
void MyMoneySeqAccessMgr::modifyTag(const MyMoneyTag& tag)
{
QMap<QString, MyMoneyTag>::ConstIterator it;
it = m_tagList.find(tag.id());
if (it == m_tagList.end()) {
QString msg = "Unknown tag '" + tag.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_tagList.modify((*it).id(), tag);
}
void MyMoneySeqAccessMgr::removeTag(const MyMoneyTag& tag)
{
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
QMap<QString, MyMoneySchedule>::ConstIterator it_s;
QMap<QString, MyMoneyTag>::ConstIterator it_ta;
it_ta = m_tagList.find(tag.id());
if (it_ta == 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 = m_transactionList.begin(); it_t != 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 = m_scheduleList.begin(); it_s != 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
removeReferences(tag.id());
m_tagList.remove((*it_ta).id());
}
const QList<MyMoneyTag> MyMoneySeqAccessMgr::tagList() const
{
return m_tagList.values();
}
void MyMoneySeqAccessMgr::addAccount(MyMoneyAccount& parent, MyMoneyAccount& account)
{
QMap<QString, MyMoneyAccount>::ConstIterator theParent;
QMap<QString, MyMoneyAccount>::ConstIterator theChild;
theParent = m_accountList.find(parent.id());
if (theParent == m_accountList.end()) {
QString msg = "Unknown parent account '";
msg += parent.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
theChild = m_accountList.find(account.id());
if (theChild == m_accountList.end()) {
QString msg = "Unknown child account '";
msg += account.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
MyMoneyAccount acc = *theParent;
acc.addAccountId(account.id());
m_accountList.modify(acc.id(), acc);
parent = acc;
acc = *theChild;
acc.setParentAccountId(parent.id());
m_accountList.modify(acc.id(), acc);
account = acc;
}
void MyMoneySeqAccessMgr::addInstitution(MyMoneyInstitution& institution)
{
MyMoneyInstitution newInstitution(nextInstitutionID(), institution);
m_institutionList.insert(newInstitution.id(), newInstitution);
// return new data
institution = newInstitution;
}
unsigned int MyMoneySeqAccessMgr::transactionCount(const QString& account) const
{
unsigned int cnt = 0;
if (account.length() == 0) {
cnt = m_transactionList.count();
} else {
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
QList<MyMoneySplit>::ConstIterator it_s;
// scan all transactions
for (it_t = m_transactionList.begin(); it_t != m_transactionList.end(); ++it_t) {
// scan all splits of this transaction
for (it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
// is it a split in our account?
if ((*it_s).accountId() == account) {
// since a transaction can only have one split referencing
// each account, we're done with the splits here!
break;
}
}
// if no split contains the account id, continue with the
// next transaction
if (it_s == (*it_t).splits().end())
continue;
// otherwise count it
++cnt;
}
}
return cnt;
}
const QMap<QString, unsigned long> MyMoneySeqAccessMgr::transactionCountMap() const
{
QMap<QString, unsigned long> map;
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
QList<MyMoneySplit>::ConstIterator it_s;
// scan all transactions
for (it_t = m_transactionList.begin(); it_t != m_transactionList.end(); ++it_t) {
// scan all splits of this transaction
for (it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
map[(*it_s).accountId()]++;
}
}
return map;
}
unsigned int MyMoneySeqAccessMgr::institutionCount() const
{
return m_institutionList.count();
}
unsigned int MyMoneySeqAccessMgr::accountCount() const
{
return m_accountList.count();
}
QString MyMoneySeqAccessMgr::nextPayeeID()
{
QString id;
id.setNum(++m_nextPayeeID);
id = 'P' + id.rightJustified(PAYEE_ID_SIZE, '0');
return id;
}
QString MyMoneySeqAccessMgr::nextTagID()
{
QString id;
id.setNum(++m_nextTagID);
id = 'G' + id.rightJustified(TAG_ID_SIZE, '0');
return id;
}
QString MyMoneySeqAccessMgr::nextInstitutionID()
{
QString id;
id.setNum(++m_nextInstitutionID);
id = 'I' + id.rightJustified(INSTITUTION_ID_SIZE, '0');
return id;
}
QString MyMoneySeqAccessMgr::nextAccountID()
{
QString id;
id.setNum(++m_nextAccountID);
id = 'A' + id.rightJustified(ACCOUNT_ID_SIZE, '0');
return id;
}
QString MyMoneySeqAccessMgr::nextTransactionID()
{
QString id;
id.setNum(++m_nextTransactionID);
id = 'T' + id.rightJustified(TRANSACTION_ID_SIZE, '0');
return id;
}
QString MyMoneySeqAccessMgr::nextScheduleID()
{
QString id;
id.setNum(++m_nextScheduleID);
id = "SCH" + id.rightJustified(SCHEDULE_ID_SIZE, '0');
return id;
}
QString MyMoneySeqAccessMgr::nextSecurityID()
{
QString id;
id.setNum(++m_nextSecurityID);
id = 'E' + id.rightJustified(SECURITY_ID_SIZE, '0');
return id;
}
QString MyMoneySeqAccessMgr::nextOnlineJobID()
{
QString id;
id.setNum(++m_nextOnlineJobID);
id = 'O' + id.rightJustified(ONLINE_JOB_ID_SIZE, '0');
return id;
}
void MyMoneySeqAccessMgr::addTransaction(MyMoneyTransaction& transaction, const bool skipAccountUpdate)
{
// 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
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) {
// 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();
m_transactionList.insert(key, newTransaction);
m_transactionKeys.insert(newTransaction.id(), key);
transaction = newTransaction;
// adjust the balance of all affected accounts
for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) {
MyMoneyAccount acc = m_accountList[(*it_s).accountId()];
adjustBalance(acc, *it_s);
if (!skipAccountUpdate) {
acc.touch();
}
m_accountList.modify(acc.id(), acc);
}
}
void MyMoneySeqAccessMgr::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
if (acc.isInvest()) {
acc.setBalance(calculateBalance(acc.id()));
} else {
acc.adjustBalance(split, reverse);
}
}
void MyMoneySeqAccessMgr::touch()
{
m_dirty = true;
m_lastModificationDate = QDate::currentDate();
}
bool MyMoneySeqAccessMgr::hasActiveSplits(const QString& id) const
{
QMap<QString, MyMoneyTransaction>::ConstIterator it;
for (it = m_transactionList.begin(); it != m_transactionList.end(); ++it) {
if ((*it).accountReferenced(id)) {
return true;
}
}
return false;
}
const MyMoneyInstitution MyMoneySeqAccessMgr::institution(const QString& id) const
{
QMap<QString, MyMoneyInstitution>::ConstIterator pos;
pos = m_institutionList.find(id);
if (pos != m_institutionList.end())
return *pos;
throw MYMONEYEXCEPTION("unknown institution");
}
const QList<MyMoneyInstitution> MyMoneySeqAccessMgr::institutionList() const
{
return m_institutionList.values();
}
void MyMoneySeqAccessMgr::modifyAccount(const MyMoneyAccount& account, const bool skipCheck)
{
QMap<QString, MyMoneyAccount>::ConstIterator pos;
// locate the account in the file global pool
pos = m_accountList.find(account.id());
if (pos != 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());
QList<QString>::ConstIterator it_a;
for (it_a = account.accountList().constBegin(); it_a != account.accountList().constEnd(); ++it_a) {
this->account(*it_a);
}
// update information in account list
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)
{
QMap<QString, MyMoneyInstitution>::ConstIterator pos;
// locate the institution in the file global pool
pos = m_institutionList.find(institution.id());
if (pos != m_institutionList.end()) {
m_institutionList.modify(institution.id(), institution);
} else
throw MYMONEYEXCEPTION("unknown institution");
}
void MyMoneySeqAccessMgr::modifyTransaction(const MyMoneyTransaction& transaction)
{
// 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
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
// 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());
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()];
if (!m_transactionList.contains(oldKey))
throw MYMONEYEXCEPTION("invalid transaction key");
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
it_t = m_transactionList.find(oldKey);
if (it_t == m_transactionList.end())
throw MYMONEYEXCEPTION("invalid transaction key");
for (it_s = (*it_t).splits().begin(); it_s != (*it_t).splits().end(); ++it_s) {
MyMoneyAccount acc = m_accountList[(*it_s).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()) {
adjustBalance(acc, *it_s, true);
acc.touch();
m_accountList.modify(acc.id(), acc);
}
}
// remove old transaction from lists
m_transactionList.remove(oldKey);
// add new transaction to lists
QString newKey = transaction.uniqueSortKey();
m_transactionList.insert(newKey, transaction);
m_transactionKeys.modify(transaction.id(), newKey);
// adjust account balances
for (it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
MyMoneyAccount acc = m_accountList[(*it_s).accountId()];
adjustBalance(acc, *it_s);
acc.touch();
m_accountList.modify(acc.id(), acc);
}
}
void MyMoneySeqAccessMgr::reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent)
{
reparentAccount(account, parent, true);
}
void MyMoneySeqAccessMgr::reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent, const bool /* sendNotification */)
{
QMap<QString, MyMoneyAccount>::ConstIterator oldParent;
QMap<QString, MyMoneyAccount>::ConstIterator newParent;
QMap<QString, MyMoneyAccount>::ConstIterator childAccount;
// verify that accounts exist. If one does not,
// an exception is thrown
MyMoneySeqAccessMgr::account(account.id());
MyMoneySeqAccessMgr::account(parent.id());
if (!account.parentAccountId().isEmpty()) {
MyMoneySeqAccessMgr::account(account.parentAccountId());
oldParent = m_accountList.find(account.parentAccountId());
}
- if (account.accountType() == MyMoneyAccount::Stock && parent.accountType() != MyMoneyAccount::Investment)
+ if (account.accountType() == eMyMoney::Account::Stock && parent.accountType() != eMyMoney::Account::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() != MyMoneyAccount::Stock && account.accountType() != MyMoneyAccount::Investment)
+ if (account.accountType() != eMyMoney::Account::Stock && account.accountType() != eMyMoney::Account::Investment)
(*childAccount).setAccountType((*newParent).accountType());
#endif
}
void MyMoneySeqAccessMgr::removeTransaction(const MyMoneyTransaction& transaction)
{
// first perform all the checks
if (transaction.id().isEmpty())
throw MYMONEYEXCEPTION("invalid transaction to be deleted");
QMap<QString, QString>::ConstIterator it_k;
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
it_k = m_transactionKeys.find(transaction.id());
if (it_k == m_transactionKeys.end())
throw MYMONEYEXCEPTION("invalid transaction to be deleted");
it_t = m_transactionList.find(*it_k);
if (it_t == 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
m_transactionList.remove(*it_k);
m_transactionKeys.remove(transaction.id());
// scan the splits and collect all accounts that need
// to be updated after the removal of this transaction
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) {
MyMoneyAccount acc = m_accountList[(*it_s).accountId()];
adjustBalance(acc, *it_s, true);
acc.touch();
m_accountList.modify(acc.id(), acc);
}
}
void MyMoneySeqAccessMgr::removeAccount(const MyMoneyAccount& account)
{
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());
// 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.
QStringList::ConstIterator it;
for (it = account.accountList().begin(); it != account.accountList().end(); ++it) {
MyMoneySeqAccessMgr::account(*it);
}
// if one of the accounts did not exist, an exception had been
// thrown and we would not make it until here.
QMap<QString, MyMoneyAccount>::ConstIterator it_a;
QMap<QString, MyMoneyAccount>::ConstIterator it_p;
// locate the account in the file global pool
it_a = m_accountList.find(account.id());
if (it_a == m_accountList.end())
throw MYMONEYEXCEPTION("Internal error: account not found in list");
it_p = m_accountList.find(parent.id());
if (it_p == 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");
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
if ((*it_a).accountList().count() > 0) {
while ((*it_a).accountList().count() > 0) {
it = (*it_a).accountList().begin();
MyMoneyAccount acc(MyMoneySeqAccessMgr::account(*it));
reparentAccount(acc, parent, false);
}
}
// remove account from parent's list
parent.removeAccountId(account.id());
m_accountList.modify(parent.id(), parent);
// remove account from the global account pool
m_accountList.remove(account.id());
}
}
void MyMoneySeqAccessMgr::removeInstitution(const MyMoneyInstitution& institution)
{
QMap<QString, MyMoneyInstitution>::ConstIterator it_i;
it_i = m_institutionList.find(institution.id());
if (it_i != m_institutionList.end()) {
m_institutionList.remove(institution.id());
} else
throw MYMONEYEXCEPTION("invalid institution");
}
void MyMoneySeqAccessMgr::transactionList(QList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const
{
list.clear();
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
QMap<QString, MyMoneyTransaction>::ConstIterator it_t_end = m_transactionList.end();
for (it_t = m_transactionList.begin(); it_t != it_t_end; ++it_t) {
// 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)
if (filter.match(*it_t)) {
unsigned int cnt = filter.matchingSplits().count();
if (cnt > 1) {
for (unsigned i = 0; i < cnt; ++i)
list.append(*it_t);
} else {
list.append(*it_t);
}
}
}
}
void MyMoneySeqAccessMgr::transactionList(QList< QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const
{
list.clear();
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
QMap<QString, MyMoneyTransaction>::ConstIterator it_t_end = m_transactionList.end();
for (it_t = m_transactionList.begin(); it_t != it_t_end; ++it_t) {
if (filter.match(*it_t)) {
const QList<MyMoneySplit>& splits = filter.matchingSplits();
QList<MyMoneySplit>::ConstIterator it_s;
QList<MyMoneySplit>::ConstIterator it_s_end = splits.end();
for (it_s = splits.constBegin(); it_s != it_s_end; ++it_s) {
list.append(qMakePair(*it_t, *it_s));
}
}
}
}
const QList<MyMoneyTransaction> MyMoneySeqAccessMgr::transactionList(MyMoneyTransactionFilter& filter) const
{
QList<MyMoneyTransaction> list;
transactionList(list, filter);
return list;
}
const QList<onlineJob> MyMoneySeqAccessMgr::onlineJobList() const
{
return m_onlineJobList.values();
}
const QList< MyMoneyCostCenter > MyMoneySeqAccessMgr::costCenterList() const
{
return m_costCenterList.values();
}
const MyMoneyCostCenter MyMoneySeqAccessMgr::costCenter(const QString& id) const
{
if (!m_costCenterList.contains(id)) {
QString msg = QString("Invalid cost center id '%1'").arg(id);
throw MYMONEYEXCEPTION(msg);
}
return m_costCenterList[id];
}
const MyMoneyTransaction MyMoneySeqAccessMgr::transaction(const QString& id) const
{
// get the full key of this transaction, throw exception
// if it's invalid (unknown)
if (!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 = m_transactionKeys[id];
if (!m_transactionList.contains(key)) {
QString msg = QString("Invalid transaction key '%1'").arg(key);
throw MYMONEYEXCEPTION(msg);
}
return m_transactionList[key];
}
const MyMoneyTransaction MyMoneySeqAccessMgr::transaction(const QString& account, const int idx) const
{
/* removed with MyMoneyAccount::Transaction
QMap<QString, MyMoneyAccount>::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<MyMoneyTransaction> list;
MyMoneyAccount acc = m_accountList[account];
MyMoneyTransactionFilter filter;
- if (acc.accountGroup() == MyMoneyAccount::Income
- || acc.accountGroup() == MyMoneyAccount::Expense)
+ if (acc.accountGroup() == eMyMoney::Account::Income
+ || acc.accountGroup() == eMyMoney::Account::Expense)
filter.addCategory(account);
else
filter.addAccount(account);
transactionList(list, filter);
if (idx < 0 || idx >= static_cast<int>(list.count()))
throw MYMONEYEXCEPTION("Unknown idx for transaction");
return transaction(list[idx].id());
}
const MyMoneyMoney MyMoneySeqAccessMgr::balance(const QString& id, const QDate& date) const
{
MyMoneyAccount acc = account(id);
if (!date.isValid()) {
// 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 (m_accountList.find(id) != m_accountList.end()) {
return m_accountList[id].balance();
}
return MyMoneyMoney();
}
return calculateBalance(id, date);
}
MyMoneyMoney MyMoneySeqAccessMgr::calculateBalance(const QString& id, const QDate& date) const
{
MyMoneyMoney balance;
QList<MyMoneyTransaction> list;
MyMoneyTransactionFilter filter;
filter.setDateFilter(QDate(), date);
filter.setReportAllSplits(false);
transactionList(list, filter);
for (QList<MyMoneyTransaction>::const_iterator it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) {
const QList<MyMoneySplit>& splits = (*it_t).splits();
for (QList<MyMoneySplit>::const_iterator it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) {
const MyMoneySplit &split = (*it_s);
if (split.accountId() != id)
continue;
if (split.action() == MyMoneySplit::ActionSplitShares) {
balance = balance * split.shares();
} else {
balance += split.shares();
}
}
}
return balance;
}
const MyMoneyMoney MyMoneySeqAccessMgr::totalBalance(const QString& id, const QDate& date) const
{
QStringList accounts;
QStringList::ConstIterator it_a;
MyMoneyMoney result(balance(id, date));
accounts = account(id).accountList();
for (it_a = accounts.constBegin(); it_a != accounts.constEnd(); ++it_a) {
result += totalBalance(*it_a, date);
}
return result;
}
void MyMoneySeqAccessMgr::loadAccounts(const QMap<QString, MyMoneyAccount>& map)
{
m_accountList = map;
// scan the map to identify the last used id
QMap<QString, MyMoneyAccount>::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) {
m_nextAccountID = lastId.mid(pos).toInt();
}
}
void MyMoneySeqAccessMgr::loadTransactions(const QMap<QString, MyMoneyTransaction>& map)
{
m_transactionList = map;
// now fill the key map and
// identify the last used id
QString lastId("");
QMap<QString, QString> keys;
QMap<QString, MyMoneyTransaction>::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();
}
m_transactionKeys = keys;
// determine highest used ID so far
int pos = lastId.indexOf(QRegExp("\\d+"), 0);
if (pos != -1) {
m_nextTransactionID = lastId.mid(pos).toInt();
}
}
void MyMoneySeqAccessMgr::loadInstitutions(const QMap<QString, MyMoneyInstitution>& map)
{
m_institutionList = map;
// scan the map to identify the last used id
QMap<QString, MyMoneyInstitution>::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) {
m_nextInstitutionID = lastId.mid(pos).toInt();
}
}
void MyMoneySeqAccessMgr::loadPayees(const QMap<QString, MyMoneyPayee>& map)
{
m_payeeList = map;
// scan the map to identify the last used id
QMap<QString, MyMoneyPayee>::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) {
m_nextPayeeID = lastId.mid(pos).toInt();
}
}
void MyMoneySeqAccessMgr::loadTags(const QMap<QString, MyMoneyTag>& map)
{
m_tagList = map;
// scan the map to identify the last used id
QMap<QString, MyMoneyTag>::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) {
m_nextTagID = lastId.mid(pos).toUInt();
}
}
void MyMoneySeqAccessMgr::loadSecurities(const QMap<QString, MyMoneySecurity>& map)
{
m_securitiesList = map;
// scan the map to identify the last used id
QMap<QString, MyMoneySecurity>::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) {
m_nextSecurityID = lastId.mid(pos).toInt();
}
}
void MyMoneySeqAccessMgr::loadCurrencies(const QMap<QString, MyMoneySecurity>& map)
{
m_currencyList = map;
}
void MyMoneySeqAccessMgr::loadPrices(const MyMoneyPriceList& list)
{
m_priceList = list;
}
void MyMoneySeqAccessMgr::loadOnlineJobs(const QMap< QString, onlineJob >& onlineJobs)
{
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) {
m_nextOnlineJobID = lastId.mid(pos).toInt();
}
}
void MyMoneySeqAccessMgr::loadCostCenters(const QMap< QString, MyMoneyCostCenter >& costCenters)
{
m_costCenterList = costCenters;
// scan the map to identify the last used id
QMap<QString, MyMoneyCostCenter>::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) {
m_nextCostCenterID = lastId.mid(pos).toInt();
}
}
void MyMoneySeqAccessMgr::loadAccountId(const unsigned long id)
{
m_nextAccountID = id;
}
void MyMoneySeqAccessMgr::loadTransactionId(const unsigned long id)
{
m_nextTransactionID = id;
}
void MyMoneySeqAccessMgr::loadPayeeId(const unsigned long id)
{
m_nextPayeeID = id;
}
void MyMoneySeqAccessMgr::loadTagId(const unsigned long id)
{
m_nextTagID = id;
}
void MyMoneySeqAccessMgr::loadInstitutionId(const unsigned long id)
{
m_nextInstitutionID = id;
}
void MyMoneySeqAccessMgr::loadSecurityId(const unsigned long id)
{
m_nextSecurityID = id;
}
void MyMoneySeqAccessMgr::loadReportId(const unsigned long id)
{
m_nextReportID = id;
}
void MyMoneySeqAccessMgr::loadBudgetId(const unsigned long id)
{
m_nextBudgetID = id;
}
void MyMoneySeqAccessMgr::loadOnlineJobId(const long unsigned int id)
{
m_nextOnlineJobID = id;
}
void MyMoneySeqAccessMgr::loadCostCenterId(const long unsigned int id)
{
m_nextCostCenterID = id;
}
const QString MyMoneySeqAccessMgr::value(const QString& key) const
{
return MyMoneyKeyValueContainer::value(key);
}
void MyMoneySeqAccessMgr::setValue(const QString& key, const QString& val)
{
MyMoneyKeyValueContainer::setValue(key, val);
touch();
}
void MyMoneySeqAccessMgr::deletePair(const QString& key)
{
MyMoneyKeyValueContainer::deletePair(key);
touch();
}
const QMap<QString, QString> MyMoneySeqAccessMgr::pairs() const
{
return MyMoneyKeyValueContainer::pairs();
}
void MyMoneySeqAccessMgr::setPairs(const QMap<QString, QString>& list)
{
MyMoneyKeyValueContainer::setPairs(list);
touch();
}
void MyMoneySeqAccessMgr::addSchedule(MyMoneySchedule& sched)
{
// 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);
m_scheduleList.insert(newSched.id(), newSched);
sched = newSched;
}
void MyMoneySeqAccessMgr::modifySchedule(const MyMoneySchedule& sched)
{
QMap<QString, MyMoneySchedule>::ConstIterator it;
it = m_scheduleList.find(sched.id());
if (it == m_scheduleList.end()) {
QString msg = "Unknown schedule '" + sched.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_scheduleList.modify(sched.id(), sched);
}
void MyMoneySeqAccessMgr::removeSchedule(const MyMoneySchedule& sched)
{
QMap<QString, MyMoneySchedule>::ConstIterator it;
it = m_scheduleList.find(sched.id());
if (it == m_scheduleList.end()) {
QString msg = "Unknown schedule '" + sched.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
// FIXME: check referential integrity for loan accounts
m_scheduleList.remove(sched.id());
}
const MyMoneySchedule MyMoneySeqAccessMgr::schedule(const QString& id) const
{
QMap<QString, MyMoneySchedule>::ConstIterator pos;
// locate the schedule and if present, return it's data
pos = m_scheduleList.find(id);
if (pos != m_scheduleList.end())
return (*pos);
// throw an exception, if it does not exist
QString msg = "Unknown schedule id '" + id + '\'';
throw MYMONEYEXCEPTION(msg);
}
const QList<MyMoneySchedule> MyMoneySeqAccessMgr::scheduleList(
const QString& accountId,
- const MyMoneySchedule::typeE type,
- const MyMoneySchedule::occurrenceE occurrence,
- const MyMoneySchedule::paymentTypeE paymentType,
+ 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
{
QMap<QString, MyMoneySchedule>::ConstIterator pos;
QList<MyMoneySchedule> list;
// qDebug("scheduleList()");
for (pos = m_scheduleList.begin(); pos != m_scheduleList.end(); ++pos) {
// qDebug(" '%s'", qPrintable((*pos).id()));
- if (type != MyMoneySchedule::TYPE_ANY) {
+ if (type != eMyMoney::Schedule::Type::Any) {
if (type != (*pos).type()) {
continue;
}
}
- if (occurrence != MyMoneySchedule::OCCUR_ANY) {
+ if (occurrence != eMyMoney::Schedule::Occurrence::Any) {
if (occurrence != (*pos).occurrence()) {
continue;
}
}
- if (paymentType != MyMoneySchedule::STYPE_ANY) {
+ if (paymentType != eMyMoney::Schedule::PaymentType::Any) {
if (paymentType != (*pos).paymentType()) {
continue;
}
}
if (!accountId.isEmpty()) {
MyMoneyTransaction t = (*pos).transaction();
QList<MyMoneySplit>::ConstIterator it;
QList<MyMoneySplit> 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<QString, MyMoneySchedule>& map)
{
m_scheduleList = map;
// scan the map to identify the last used id
QMap<QString, MyMoneySchedule>::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) {
m_nextScheduleID = lastId.mid(pos).toInt();
}
}
void MyMoneySeqAccessMgr::loadScheduleId(const unsigned long id)
{
m_nextScheduleID = id;
}
const QList<MyMoneySchedule> MyMoneySeqAccessMgr::scheduleListEx(int scheduleTypes,
int scheduleOcurrences,
int schedulePaymentTypes,
QDate date,
const QStringList& accounts) const
{
// qDebug("scheduleListEx");
QMap<QString, MyMoneySchedule>::ConstIterator pos;
QList<MyMoneySchedule> list;
if (!date.isValid())
return list;
for (pos = m_scheduleList.begin(); pos != m_scheduleList.end(); ++pos) {
- if (scheduleTypes && !(scheduleTypes & (*pos).type()))
+ if (scheduleTypes && !(scheduleTypes & (int)(*pos).type()))
continue;
- if (scheduleOcurrences && !(scheduleOcurrences & (*pos).occurrence()))
+ if (scheduleOcurrences && !(scheduleOcurrences & (int)(*pos).occurrence()))
continue;
- if (schedulePaymentTypes && !(schedulePaymentTypes & (*pos).paymentType()))
+ 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)
{
// create the account
MyMoneySecurity newSecurity(nextSecurityID(), security);
m_securitiesList.insert(newSecurity.id(), newSecurity);
security = newSecurity;
}
void MyMoneySeqAccessMgr::modifySecurity(const MyMoneySecurity& security)
{
QMap<QString, MyMoneySecurity>::ConstIterator it;
it = m_securitiesList.find(security.id());
if (it == m_securitiesList.end()) {
QString msg = "Unknown security '";
msg += security.id() + "' during modifySecurity()";
throw MYMONEYEXCEPTION(msg);
}
m_securitiesList.modify(security.id(), security);
}
void MyMoneySeqAccessMgr::removeSecurity(const MyMoneySecurity& security)
{
QMap<QString, MyMoneySecurity>::ConstIterator it;
// FIXME: check referential integrity
it = m_securitiesList.find(security.id());
if (it == m_securitiesList.end()) {
QString msg = "Unknown security '";
msg += security.id() + "' during removeSecurity()";
throw MYMONEYEXCEPTION(msg);
}
m_securitiesList.remove(security.id());
}
const MyMoneySecurity MyMoneySeqAccessMgr::security(const QString& id) const
{
QMap<QString, MyMoneySecurity>::ConstIterator it = m_securitiesList.find(id);
if (it != m_securitiesList.end()) {
return it.value();
}
return MyMoneySecurity();
}
const QList<MyMoneySecurity> MyMoneySeqAccessMgr::securityList() const
{
//qDebug("securityList: Security list size is %d, this=%8p", m_equitiesList.size(), (void*)this);
return m_securitiesList.values();
}
void MyMoneySeqAccessMgr::addCurrency(const MyMoneySecurity& currency)
{
QMap<QString, MyMoneySecurity>::ConstIterator it;
it = m_currencyList.find(currency.id());
if (it != m_currencyList.end()) {
throw MYMONEYEXCEPTION(i18n("Cannot add currency with existing id %1", currency.id()));
}
m_currencyList.insert(currency.id(), currency);
}
void MyMoneySeqAccessMgr::modifyCurrency(const MyMoneySecurity& currency)
{
QMap<QString, MyMoneySecurity>::ConstIterator it;
it = m_currencyList.find(currency.id());
if (it == m_currencyList.end()) {
throw MYMONEYEXCEPTION(i18n("Cannot modify currency with unknown id %1", currency.id()));
}
m_currencyList.modify(currency.id(), currency);
}
void MyMoneySeqAccessMgr::removeCurrency(const MyMoneySecurity& currency)
{
QMap<QString, MyMoneySecurity>::ConstIterator it;
// FIXME: check referential integrity
it = m_currencyList.find(currency.id());
if (it == m_currencyList.end()) {
throw MYMONEYEXCEPTION(i18n("Cannot remove currency with unknown id %1", currency.id()));
}
m_currencyList.remove(currency.id());
}
const MyMoneySecurity MyMoneySeqAccessMgr::currency(const QString& id) const
{
if (id.isEmpty()) {
}
QMap<QString, MyMoneySecurity>::ConstIterator it;
it = m_currencyList.find(id);
if (it == m_currencyList.end()) {
throw MYMONEYEXCEPTION(i18n("Cannot retrieve currency with unknown id '%1'", id));
}
return *it;
}
const QList<MyMoneySecurity> MyMoneySeqAccessMgr::currencyList() const
{
return m_currencyList.values();
}
const QList<MyMoneyReport> MyMoneySeqAccessMgr::reportList() const
{
return m_reportList.values();
}
void MyMoneySeqAccessMgr::addReport(MyMoneyReport& report)
{
if (!report.id().isEmpty())
throw MYMONEYEXCEPTION("report already contains an id");
MyMoneyReport newReport(nextReportID(), report);
m_reportList.insert(newReport.id(), newReport);
report = newReport;
}
void MyMoneySeqAccessMgr::loadReports(const QMap<QString, MyMoneyReport>& map)
{
m_reportList = map;
// scan the map to identify the last used id
QMap<QString, MyMoneyReport>::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) {
m_nextReportID = lastId.mid(pos).toInt();
}
}
void MyMoneySeqAccessMgr::modifyReport(const MyMoneyReport& report)
{
QMap<QString, MyMoneyReport>::ConstIterator it;
it = m_reportList.find(report.id());
if (it == m_reportList.end()) {
QString msg = "Unknown report '" + report.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_reportList.modify(report.id(), report);
}
QString MyMoneySeqAccessMgr::nextReportID()
{
QString id;
id.setNum(++m_nextReportID);
id = 'R' + id.rightJustified(REPORT_ID_SIZE, '0');
return id;
}
unsigned MyMoneySeqAccessMgr::countReports() const
{
return m_reportList.count();
}
const MyMoneyReport MyMoneySeqAccessMgr::report(const QString& _id) const
{
return m_reportList[_id];
}
void MyMoneySeqAccessMgr::removeReport(const MyMoneyReport& report)
{
QMap<QString, MyMoneyReport>::ConstIterator it;
it = m_reportList.find(report.id());
if (it == m_reportList.end()) {
QString msg = "Unknown report '" + report.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_reportList.remove(report.id());
}
const QList<MyMoneyBudget> MyMoneySeqAccessMgr::budgetList() const
{
return m_budgetList.values();
}
void MyMoneySeqAccessMgr::addBudget(MyMoneyBudget& budget)
{
MyMoneyBudget newBudget(nextBudgetID(), budget);
m_budgetList.insert(newBudget.id(), newBudget);
budget = newBudget;
}
void MyMoneySeqAccessMgr::loadBudgets(const QMap<QString, MyMoneyBudget>& map)
{
m_budgetList = map;
// scan the map to identify the last used id
QMap<QString, MyMoneyBudget>::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) {
m_nextBudgetID = lastId.mid(pos).toInt();
}
}
const MyMoneyBudget MyMoneySeqAccessMgr::budgetByName(const QString& budget) const
{
QMap<QString, MyMoneyBudget>::ConstIterator it_p;
for (it_p = m_budgetList.begin(); it_p != m_budgetList.end(); ++it_p) {
if ((*it_p).name() == budget) {
return *it_p;
}
}
throw MYMONEYEXCEPTION("Unknown budget '" + budget + '\'');
}
void MyMoneySeqAccessMgr::modifyBudget(const MyMoneyBudget& budget)
{
QMap<QString, MyMoneyBudget>::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);
}
QString MyMoneySeqAccessMgr::nextBudgetID()
{
QString id;
id.setNum(++m_nextBudgetID);
id = 'B' + id.rightJustified(BUDGET_ID_SIZE, '0');
return id;
}
unsigned MyMoneySeqAccessMgr::countBudgets() const
{
return m_budgetList.count();
}
MyMoneyBudget MyMoneySeqAccessMgr::budget(const QString& _id) const
{
return m_budgetList[_id];
}
void MyMoneySeqAccessMgr::removeBudget(const MyMoneyBudget& budget)
{
QMap<QString, MyMoneyBudget>::ConstIterator it;
it = m_budgetList.find(budget.id());
if (it == m_budgetList.end()) {
QString msg = "Unknown budget '" + budget.id() + '\'';
throw MYMONEYEXCEPTION(msg);
}
m_budgetList.remove(budget.id());
}
void MyMoneySeqAccessMgr::addPrice(const MyMoneyPrice& price)
{
MyMoneySecurityPair pricePair(price.from(), price.to());
QMap<MyMoneySecurityPair, MyMoneyPriceEntries>::ConstIterator it_m;
it_m = m_priceList.find(pricePair);
MyMoneyPriceEntries entries;
if (it_m != 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 != m_priceList.end()) {
m_priceList.modify(pricePair, entries);
} else {
m_priceList.insert(pricePair, entries);
}
}
void MyMoneySeqAccessMgr::removePrice(const MyMoneyPrice& price)
{
MyMoneySecurityPair pricePair(price.from(), price.to());
QMap<MyMoneySecurityPair, MyMoneyPriceEntries>::ConstIterator it_m;
it_m = m_priceList.find(pricePair);
MyMoneyPriceEntries entries;
if (it_m != m_priceList.end()) {
entries = (*it_m);
}
// store new value in local copy
entries.remove(price.date());
if (entries.count() != 0) {
m_priceList.modify(pricePair, entries);
} else {
m_priceList.remove(pricePair);
}
}
const MyMoneyPriceList MyMoneySeqAccessMgr::priceList() const
{
MyMoneyPriceList list;
m_priceList.map(list);
return list;
}
MyMoneyPrice MyMoneySeqAccessMgr::price(const QString& fromId, const QString& toId, const QDate& _date, const bool exactDate) const
{
// if the caller selected an exact entry, we can search for it using the date as the key
QMap<MyMoneySecurityPair, MyMoneyPriceEntries>::const_iterator itm = m_priceList.find(qMakePair(fromId, toId));
if (itm != 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()
{
// reset the balance of all accounts to 0
QMap<QString, MyMoneyAccount> map;
m_accountList.map(map);
QMap<QString, MyMoneyAccount>::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
QMap<QString, MyMoneyTransaction>::const_iterator it_t;
for (it_t = m_transactionList.begin(); it_t != m_transactionList.end(); ++it_t) {
const QList<MyMoneySplit>& splits = (*it_t).splits();
QList<MyMoneySplit>::const_iterator it_s = splits.begin();
for (; it_s != splits.end(); ++it_s) {
if (!(*it_s).shares().isZero()) {
const QString& id = (*it_s).accountId();
// locate the account and if present, update data
if (map.find(id) != map.end()) {
map[id].adjustBalance(*it_s);
}
}
}
}
m_accountList = map;
}
bool MyMoneySeqAccessMgr::isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const
{
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
// 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, m_transactionList)
if (it.hasReferenceTo(id))
return true;
if (!skipCheck.testBit((int)Reference::Account))
foreach (const auto it, m_accountList)
if (it.hasReferenceTo(id))
return true;
if (!skipCheck.testBit((int)Reference::Institution))
foreach (const auto it, m_institutionList)
if (it.hasReferenceTo(id))
return true;
if (!skipCheck.testBit((int)Reference::Payee))
foreach (const auto it, m_payeeList)
if (it.hasReferenceTo(id))
return true;
if (!skipCheck.testBit((int)Reference::Tag))
foreach (const auto it, m_tagList)
if (it.hasReferenceTo(id))
return true;
if (!skipCheck.testBit((int)Reference::Budget))
foreach (const auto it, m_budgetList)
if (it.hasReferenceTo(id))
return true;
if (!skipCheck.testBit((int)Reference::Schedule))
foreach (const auto it, m_scheduleList)
if (it.hasReferenceTo(id))
return true;
if (!skipCheck.testBit((int)Reference::Security))
foreach (const auto it, m_securitiesList)
if (it.hasReferenceTo(id))
return true;
if (!skipCheck.testBit((int)Reference::Currency))
foreach (const auto it, 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 = m_priceList.begin(); it_pr != m_priceList.end(); ++it_pr) {
if ((it_pr.key().first == id) || (it_pr.key().second == id))
return true;
}
}
return false;
}
void MyMoneySeqAccessMgr::startTransaction()
{
m_payeeList.startTransaction(&m_nextPayeeID);
m_tagList.startTransaction(&m_nextTagID);
m_institutionList.startTransaction(&m_nextInstitutionID);
m_accountList.startTransaction(&m_nextPayeeID);
m_transactionList.startTransaction(&m_nextTransactionID);
m_transactionKeys.startTransaction();
m_scheduleList.startTransaction(&m_nextScheduleID);
m_securitiesList.startTransaction(&m_nextSecurityID);
m_currencyList.startTransaction();
m_reportList.startTransaction(&m_nextReportID);
m_budgetList.startTransaction(&m_nextBudgetID);
m_priceList.startTransaction();
m_onlineJobList.startTransaction(&m_nextOnlineJobID);
}
bool MyMoneySeqAccessMgr::commitTransaction()
{
bool rc = false;
rc |= m_payeeList.commitTransaction();
rc |= m_tagList.commitTransaction();
rc |= m_institutionList.commitTransaction();
rc |= m_accountList.commitTransaction();
rc |= m_transactionList.commitTransaction();
rc |= m_transactionKeys.commitTransaction();
rc |= m_scheduleList.commitTransaction();
rc |= m_securitiesList.commitTransaction();
rc |= m_currencyList.commitTransaction();
rc |= m_reportList.commitTransaction();
rc |= m_budgetList.commitTransaction();
rc |= m_priceList.commitTransaction();
rc |= m_onlineJobList.commitTransaction();
// if there was a change, touch the whole storage object
if (rc)
touch();
return rc;
}
void MyMoneySeqAccessMgr::rollbackTransaction()
{
m_payeeList.rollbackTransaction();
m_tagList.rollbackTransaction();
m_institutionList.rollbackTransaction();
m_accountList.rollbackTransaction();
m_transactionList.rollbackTransaction();
m_transactionKeys.rollbackTransaction();
m_scheduleList.rollbackTransaction();
m_securitiesList.rollbackTransaction();
m_currencyList.rollbackTransaction();
m_reportList.rollbackTransaction();
m_budgetList.rollbackTransaction();
m_priceList.rollbackTransaction();
m_onlineJobList.rollbackTransaction();
}
void MyMoneySeqAccessMgr::removeReferences(const QString& id)
{
QMap<QString, MyMoneyReport>::const_iterator it_r;
QMap<QString, MyMoneyBudget>::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);
}
}
#undef TRY
#undef CATCH
#undef PASS
diff --git a/kmymoney/mymoney/storage/mymoneyseqaccessmgr.h b/kmymoney/mymoney/storage/mymoneyseqaccessmgr.h
index 0977913b7..d37ecb9a8 100644
--- a/kmymoney/mymoney/storage/mymoneyseqaccessmgr.h
+++ b/kmymoney/mymoney/storage/mymoneyseqaccessmgr.h
@@ -1,1401 +1,1401 @@
/***************************************************************************
mymoneyseqaccessmgr.h - description
-------------------
begin : Sun May 5 2002
copyright : (C) 2000-2002 by Michael Edwardes <mte@users.sourceforge.net>
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@users.sourceforge.net>
2017 by Łukasz Wojniłowicz <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 MYMONEYSEQACCESSMGR_H
#define MYMONEYSEQACCESSMGR_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
// ----------------------------------------------------------------------------
// Project Includes
#include "imymoneystorage.h"
#include "imymoneyserialize.h"
#include "mymoneymap.h"
/**
* @author Thomas Baumgart
*/
/**
* The MyMoneySeqAccessMgr 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.
*
* 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
* 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
* of the method dirty() can be used.
*/
class MyMoneySeqAccessMgr : public IMyMoneyStorage, public IMyMoneySerialize,
public MyMoneyKeyValueContainer
{
KMM_MYMONEY_UNIT_TESTABLE
public:
MyMoneySeqAccessMgr();
~MyMoneySeqAccessMgr();
// general get functions
const MyMoneyPayee& user() const {
return m_user;
};
const QDate creationDate() const {
return m_creationDate;
};
const QDate lastModificationDate() const {
return m_lastModificationDate;
};
unsigned int currentFixVersion() const {
return m_currentFixVersion;
};
unsigned int fileFixVersion() const {
return m_fileFixVersion;
};
// general set functions
void setUser(const MyMoneyPayee& user) {
m_user = user;
touch();
};
void setCreationDate(const QDate& val) {
m_creationDate = val; touch();
};
void setLastModificationDate(const QDate& val) {
m_lastModificationDate = val; m_dirty = false;
};
void setFileFixVersion(const unsigned int v) {
m_fileFixVersion = v;
};
/**
* This method is used to get a SQL reader for subsequent database access
*/
QExplicitlySharedDataPointer <MyMoneyStorageSql> connectToDatabase(const QUrl &url);
/**
* 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() { };
/**
* 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();
/**
* 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
*/
const 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;
/**
* 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 STD_ACC_LIABILITY
* @li STD_ACC_ASSET
* @li STD_ACC_EXPENSE
* @li STD_ACC_INCOME
* @li STD_ACC_EQUITY
*
* @param name QString reference to the name to be set
*
*/
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);
/**
* 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);
/**
* Create now onlineJob
*/
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
*/
const 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
*/
const 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 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<MyMoneyPayee> containing the payee information
*/
const QList<MyMoneyPayee> 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
*/
const 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
*/
const 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 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<MyMoneyTag> containing the tag information
*/
const QList<MyMoneyTag> 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);
/**
* 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);
/**
* 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, const 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, const 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);
/**
* 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);
/** @todo implement */
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);
/**
* 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);
/**
* 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 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);
const onlineJob getOnlineJob(const QString &id) const;
/** @todo implement */
long unsigned int onlineJobId() const {
return 1;
}
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
*/
const 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
*/
const 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;
/**
* 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
*/
const MyMoneyMoney balance(const QString& id, const QDate& date = QDate()) 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
*/
const MyMoneyMoney totalBalance(const QString& id, const QDate& date = QDate()) 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
*/
const 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 {
return m_dirty;
}
/**
* 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() {
m_dirty = true;
};
/**
* This method returns a list of the institutions
* inside a MyMoneyFile object
*
* @return QMap containing the institution information
*/
const QList<MyMoneyInstitution> 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<MyMoneyAccount>& 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<MyMoneyTransaction>
*/
void transactionList(QList<MyMoneyTransaction>& 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<QPair<MyMoneyTransaction,MyMoneySplit> >
*/
void transactionList(QList< QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const;
/**
* Compatibility interface for the previous method.
*/
const QList<MyMoneyTransaction> transactionList(MyMoneyTransactionFilter& filter) const;
/**
* @brief Return all onlineJobs
*/
const QList<onlineJob> onlineJobList() const;
/**
* @brief Return all cost center objects
*/
const QList< MyMoneyCostCenter > costCenterList() const;
/**
* @brief Return cost center object by id
*/
const 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 {
return m_transactionKeys.contains(id);
}
/**
* 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 = QString()) const;
const QMap<QString, unsigned long> 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;
/**
* This method is used to return the standard liability account
* @return MyMoneyAccount liability account(group)
*/
const MyMoneyAccount liability() const {
return account(STD_ACC_LIABILITY);
};
/**
* This method is used to return the standard asset account
* @return MyMoneyAccount asset account(group)
*/
const MyMoneyAccount asset() const {
return account(STD_ACC_ASSET);
};
/**
* This method is used to return the standard expense account
* @return MyMoneyAccount expense account(group)
*/
const MyMoneyAccount expense() const {
return account(STD_ACC_EXPENSE);
};
/**
* This method is used to return the standard income account
* @return MyMoneyAccount income account(group)
*/
const MyMoneyAccount income() const {
return account(STD_ACC_INCOME);
};
/**
* This method is used to return the standard equity account
* @return MyMoneyAccount equity account(group)
*/
const MyMoneyAccount equity() const {
return account(STD_ACC_EQUITY);
};
virtual void loadAccounts(const QMap<QString, MyMoneyAccount>& acc);
virtual void loadTransactions(const QMap<QString, MyMoneyTransaction>& map);
virtual void loadInstitutions(const QMap<QString, MyMoneyInstitution>& map);
virtual void loadPayees(const QMap<QString, MyMoneyPayee>& map);
virtual void loadTags(const QMap<QString, MyMoneyTag>& map);
virtual void loadSchedules(const QMap<QString, MyMoneySchedule>& map);
virtual void loadSecurities(const QMap<QString, MyMoneySecurity>& map);
virtual void loadCurrencies(const QMap<QString, MyMoneySecurity>& map);
virtual void loadPrices(const MyMoneyPriceList& list);
virtual void loadOnlineJobs(const QMap<QString, onlineJob>& onlineJobs);
virtual void loadCostCenters(const QMap<QString, MyMoneyCostCenter>& costCenters);
virtual void loadAccountId(const unsigned long id);
virtual void loadTransactionId(const unsigned long id);
virtual void loadPayeeId(const unsigned long id);
virtual void loadTagId(const unsigned long id);
virtual void loadInstitutionId(const unsigned long id);
virtual void loadScheduleId(const unsigned long id);
virtual void loadSecurityId(const unsigned long id);
virtual void loadReportId(const unsigned long id);
virtual void loadBudgetId(const unsigned long id);
virtual void loadOnlineJobId(const unsigned long id);
virtual void loadCostCenterId(const unsigned long id);
virtual unsigned long accountId() const {
return m_nextAccountID;
};
virtual unsigned long transactionId() const {
return m_nextTransactionID;
};
virtual unsigned long payeeId() const {
return m_nextPayeeID;
};
virtual unsigned long tagId() const {
return m_nextTagID;
};
virtual unsigned long institutionId() const {
return m_nextInstitutionID;
};
virtual unsigned long scheduleId() const {
return m_nextScheduleID;
};
virtual unsigned long securityId() const {
return m_nextSecurityID;
};
virtual unsigned long reportId() const {
return m_nextReportID;
};
virtual unsigned long budgetId() const {
return m_nextBudgetID;
};
virtual unsigned long costCenterId() const {
return m_nextCostCenterID;
}
/**
* 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
*/
const QString value(const QString& key) const;
/**
* 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);
/**
* 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);
// documented in IMyMoneySerialize base class
const QMap<QString, QString> pairs() const;
// documented in IMyMoneySerialize base class
void setPairs(const QMap<QString, QString>& 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);
/**
* 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
*/
const 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
*/
virtual 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 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
*/
const MyMoneySecurity security(const QString& id) const;
/**
* This method returns a list of security objects that the engine has
* knowledge of.
*/
const QList<MyMoneySecurity> 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);
/**
* 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);
/**
* 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);
/**
* 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
*/
const 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.
*/
const QList<MyMoneySecurity> 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 MyMoneySchedule::typeE for details.
- * Default is MyMoneySchedule::TYPE_ANY
+ * 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 MyMoneySchedule::occurrenceE for details.
- * Default is MyMoneySchedule::OCCUR_ANY
+ * See eMyMoney::Schedule::Occurence for details.
+ * Default is eMyMoney::Schedule::Occurrence::Any
* @param paymentType only schedules of payment method @p paymentType
* are searched for.
- * See MyMoneySchedule::paymentTypeE for details.
- * Default is MyMoneySchedule::STYPE_ANY
+ * 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<MyMoneySchedule> list of schedule objects.
*/
const QList<MyMoneySchedule> scheduleList(const QString& accountId = QString(),
- const MyMoneySchedule::typeE type = MyMoneySchedule::TYPE_ANY,
- const MyMoneySchedule::occurrenceE occurrence = MyMoneySchedule::OCCUR_ANY,
- const MyMoneySchedule::paymentTypeE paymentType = MyMoneySchedule::STYPE_ANY,
+ const eMyMoney::Schedule::Type type = eMyMoney::Schedule::Type::Any,
+ const eMyMoney::Schedule::Occurrence occurrence = eMyMoney::Schedule::Occurrence::Any,
+ const eMyMoney::Schedule::PaymentType paymentType = eMyMoney::Schedule::PaymentType::Any,
const QDate& startDate = QDate(),
const QDate& endDate = QDate(),
const bool overdue = false) const;
const QList<MyMoneySchedule> scheduleListEx(int scheduleTypes,
int scheduleOcurrences,
int schedulePaymentTypes,
QDate startDate,
const QStringList& accounts = QStringList()) 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.
*/
const QList<MyMoneyReport> 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);
/**
* 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<QString, MyMoneyReport>& 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);
/**
* 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
*/
const 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.
*/
const QList<MyMoneyBudget> 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);
/**
* 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<QString, MyMoneyBudget>& 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
*/
const 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);
/**
* 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 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 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, const bool exactDate) const;
/**
* This method returns a list of all price entries.
*/
const 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;
/**
* This method recalculates the balances of all accounts
* based on the transactions stored in the engine.
*/
void rebuildAccountBalances();
virtual void startTransaction();
virtual bool commitTransaction();
virtual void rollbackTransaction();
protected:
void removeReferences(const QString& id);
/**
* 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 = QDate()) const;
private:
static const int INSTITUTION_ID_SIZE = 6;
static const int ACCOUNT_ID_SIZE = 6;
static const int TRANSACTION_ID_SIZE = 18;
static const int PAYEE_ID_SIZE = 6;
static const int TAG_ID_SIZE = 6;
static const int SCHEDULE_ID_SIZE = 6;
static const int SECURITY_ID_SIZE = 6;
static const int REPORT_ID_SIZE = 6;
static const int BUDGET_ID_SIZE = 6;
static const int ONLINE_JOB_ID_SIZE = 6;
static const int COSTCENTER_ID_SIZE = 6;
/**
* This method is used to set the dirty flag and update the
* date of the last modification.
*/
void touch();
/**
* 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 = false);
/**
* 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().
*/
unsigned long m_nextInstitutionID;
/**
* The member variable m_nextAccountID keeps the number that will be
* assigned to the next institution created. It is maintained by
* nextAccountID().
*/
unsigned long m_nextAccountID;
/**
* The member variable m_nextTransactionID keeps the number that will be
* assigned to the next transaction created. It is maintained by
* nextTransactionID().
*/
unsigned long m_nextTransactionID;
/**
* The member variable m_nextPayeeID keeps the number that will be
* assigned to the next payee created. It is maintained by
* nextPayeeID()
*/
unsigned long m_nextPayeeID;
/**
* The member variable m_nextTagID keeps the number that will be
* assigned to the next tag created. It is maintained by
* nextTagID()
*/
unsigned long m_nextTagID;
/**
* The member variable m_nextScheduleID keeps the number that will be
* assigned to the next schedule created. It is maintained by
* nextScheduleID()
*/
unsigned long 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()
*/
unsigned long m_nextSecurityID;
unsigned long 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()
*/
unsigned long m_nextBudgetID;
/**
* This member variable keeps the number that will be assigned to the
* next onlineJob object created. It is maintained by nextOnlineJobID()
*/
unsigned long m_nextOnlineJobID;
/**
* This member variable keeps the number that will be assigned to the
* next cost center object created. It is maintained by nextCostCenterID()
*/
unsigned long m_nextCostCenterID;
/**
* The member variable m_institutionList is the container for the
* institutions known within this file.
*/
MyMoneyMap<QString, MyMoneyInstitution> m_institutionList;
/**
* The member variable m_accountList is the container for the accounts
* known within this file.
*/
MyMoneyMap<QString, MyMoneyAccount> m_accountList;
/**
* The member variable m_transactionList is the container for all
* transactions within this file.
* @see m_transactionKeys
*/
MyMoneyMap<QString, MyMoneyTransaction> 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<QString, QString> m_transactionKeys;
/**
* A list containing all the payees that have been used
*/
MyMoneyMap<QString, MyMoneyPayee> m_payeeList;
/**
* A list containing all the tags that have been used
*/
MyMoneyMap<QString, MyMoneyTag> m_tagList;
/**
* A list containing all the scheduled transactions
*/
MyMoneyMap<QString, MyMoneySchedule> 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<QString, MyMoneySecurity> m_securitiesList;
/**
* A list containing all the currency information objects.
*/
MyMoneyMap<QString, MyMoneySecurity> m_currencyList;
MyMoneyMap<QString, MyMoneyReport> m_reportList;
/**
* A list containing all the budget information objects.
*/
MyMoneyMap<QString, MyMoneyBudget> m_budgetList;
MyMoneyMap<MyMoneySecurityPair, MyMoneyPriceEntries> m_priceList;
/**
* A list containing all the onlineJob information objects.
*/
MyMoneyMap<QString, onlineJob> m_onlineJobList;
/**
* A list containing all the cost center information objects
*/
MyMoneyMap<QString, MyMoneyCostCenter> 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
* 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.
*/
QDate m_lastModificationDate;
/**
* This member variable contains the current fix level of application
* data files. (see kmymoneyview.cpp)
*/
unsigned int m_currentFixVersion;
/**
* This member variable contains the current fix level of the
* presently open data file. (see kmymoneyview.cpp)
*/
unsigned int m_fileFixVersion;
/**
* This method is used to get the next valid ID for a institution
* @return id for a institution
*/
QString nextInstitutionID();
/**
* This method is used to get the next valid ID for an account
* @return id for an account
*/
QString nextAccountID();
/**
* This method is used to get the next valid ID for a transaction
* @return id for a transaction
*/
QString nextTransactionID();
/**
* This method is used to get the next valid ID for a payee
* @return id for a payee
*/
QString nextPayeeID();
/**
* This method is used to get the next valid ID for a tag
* @return id for a tag
*/
QString nextTagID();
/**
* This method is used to get the next valid ID for a scheduled transaction
* @return id for a scheduled transaction
*/
QString nextScheduleID();
/**
* This method is used to get the next valid ID for an security object.
* @return id for an security object
*/
QString nextSecurityID();
QString nextReportID();
/**
* This method is used to get the next valid ID for a budget object.
* @return id for an budget object
*/
QString nextBudgetID();
/**
* This method is used to get the next valid ID for an onlineJob object.
*/
QString nextOnlineJobID();
/**
* 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, const bool sendNotification);
/**
* This method will close a database and log the use roff
*/
void close() {}
/**
* 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/mymoneystoragedump.cpp b/kmymoney/mymoney/storage/mymoneystoragedump.cpp
index e8fa1e8d2..b15fef490 100644
--- a/kmymoney/mymoney/storage/mymoneystoragedump.cpp
+++ b/kmymoney/mymoney/storage/mymoneystoragedump.cpp
@@ -1,485 +1,485 @@
/***************************************************************************
mymoneystoragedump.cpp - description
-------------------
begin : Sun May 5 2002
copyright : (C) 2000-2002 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "mymoneystoragedump.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QString>
#include <QDate>
#include <QList>
#include <QStringList>
#include <QTextStream>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "imymoneyserialize.h"
#include "imymoneystorage.h"
#include "mymoneyaccount.h"
#include "mymoneysecurity.h"
#include "mymoneyprice.h"
MyMoneyStorageDump::MyMoneyStorageDump()
{
}
MyMoneyStorageDump::~MyMoneyStorageDump()
{
}
void MyMoneyStorageDump::readStream(QDataStream& /* s */, IMyMoneySerialize* /* storage */)
{
qDebug("Reading not supported by MyMoneyStorageDump!!");
}
void MyMoneyStorageDump::writeStream(QDataStream& _s, IMyMoneySerialize* _storage)
{
QTextStream s(_s.device());
IMyMoneyStorage* storage = dynamic_cast<IMyMoneyStorage *>(_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<MyMoneyAccount> list_a;
storage->accountList(list_a);
s << "accounts = " << list_a.count() << ", next id = " << _storage->accountId() << "\n";
MyMoneyTransactionFilter filter;
filter.setReportAllSplits(false);
QList<MyMoneyTransaction> list_t;
storage->transactionList(list_t, filter);
QList<MyMoneyTransaction>::ConstIterator it_t;
s << "transactions = " << list_t.count() << ", next id = " << _storage->transactionId() << "\n";
QMap<int, int> xferCount;
for (it_t = list_t.constBegin(); it_t != list_t.constEnd(); ++it_t) {
QList<MyMoneySplit>::ConstIterator it_s;
int accountCount = 0;
for (it_s = (*it_t).splits().constBegin(); it_s != (*it_t).splits().constEnd(); ++it_s) {
MyMoneyAccount acc = storage->account((*it_s).accountId());
- if (acc.accountGroup() != MyMoneyAccount::Expense
- && acc.accountGroup() != MyMoneyAccount::Income)
+ if (acc.accountGroup() != eMyMoney::Account::Expense
+ && acc.accountGroup() != eMyMoney::Account::Income)
accountCount++;
}
if (accountCount > 1)
xferCount[accountCount] = xferCount[accountCount] + 1;
}
QMap<int, int>::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 << "schedules = " << _storage->scheduleList().count() << ", next id = " << _storage->scheduleId() << "\n";
s << "\n";
s << "Institutions\n";
s << "------------\n";
QList<MyMoneyInstitution> list_i = storage->institutionList();
QList<MyMoneyInstitution>::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<MyMoneyPayee> list_p = storage->payeeList();
QList<MyMoneyPayee>::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<MyMoneyTag> list_ta = storage->tagList();
QList<MyMoneyTag>::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<MyMoneyAccount>::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 = " << (*it_a).accountType() << "\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<MyMoneyCurrency> list_c = storage->currencyList();
QList<MyMoneyCurrency>::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<MyMoneySecurity> list_e = storage->securityList();
QList<MyMoneySecurity>::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";
QMap<QString, QString>kvp = (*it_e).pairs();
QMap<QString, QString>::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";
QList<MyMoneySchedule> list_s = storage->scheduleList();
QList<MyMoneySchedule>::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";
s << " Type = " << MyMoneySchedule::scheduleTypeToString((*it_s).type()) << "\n";
s << " Paymenttype = " << MyMoneySchedule::paymentMethodToString((*it_s).paymentType()) << "\n";
s << " Fixed = " << (*it_s).isFixed() << "\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<QDate> list_d;
QList<QDate>::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<MyMoneyReport> list_r = storage->reportList();
QList<MyMoneyReport>::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<MyMoneyBudget> list_b = storage->budgetList();
QList<MyMoneyBudget>::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<QString, QString>::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)
{
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";
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = it_t.splits().constBegin(); it_s != it_t.splits().constEnd(); ++it_s) {
s << " ID = " << (*it_s).id() << "\n";
s << " Transaction = " << (*it_s).transactionId() << "\n";
s << " Payee = " << (*it_s).payeeId();
if (!(*it_s).payeeId().isEmpty()) {
MyMoneyPayee p = storage->payee((*it_s).payeeId());
s << " (" << p.name() << ")" << "\n";
} else
s << " ()\n";
for (int i = 0; i < (*it_s).tagIdList().size(); i++) {
s << " Tag = " << (*it_s).tagIdList()[i];
if (!(*it_s).tagIdList()[i].isEmpty()) {
MyMoneyTag ta = storage->tag((*it_s).tagIdList()[i]);
s << " (" << ta.name() << ")" << "\n";
} else
s << " ()\n";
}
s << " Account = " << (*it_s).accountId();
MyMoneyAccount acc;
try {
acc = storage->account((*it_s).accountId());
s << " (" << acc.name() << ") [" << acc.currencyId() << "]\n";
} catch (const MyMoneyException &) {
s << " (---) [---]\n";
}
s << " Memo = " << (*it_s).memo() << "\n";
if ((*it_s).value() == MyMoneyMoney::autoCalc)
s << " Value = will be calculated" << "\n";
else
s << " Value = " << (*it_s).value().formatMoney("", 2)
<< " (" << (*it_s).value().toString() << ")\n";
s << " Shares = " << (*it_s).shares().formatMoney("", 2)
<< " (" << (*it_s).shares().toString() << ")\n";
s << " Action = '" << (*it_s).action() << "'\n";
s << " Nr = '" << (*it_s).number() << "'\n";
s << " ReconcileFlag = '" << reconcileToString((*it_s).reconcileFlag()) << "'\n";
if ((*it_s).reconcileFlag() != MyMoneySplit::NotReconciled) {
s << " ReconcileDate = " << (*it_s).reconcileDate().toString(Qt::ISODate) << "\n";
}
s << " BankID = " << (*it_s).bankID() << "\n";
dumpKVP("KVP:", s, (*it_s), 4);
s << "\n";
}
s << "\n";
}
#define i18n QString
const QString MyMoneyStorageDump::reconcileToString(MyMoneySplit::reconcileFlagE flag) const
{
QString rc;
switch (flag) {
case MyMoneySplit::NotReconciled:
rc = i18nc("Reconciliation status 'Not Reconciled'", "not reconciled");
break;
case MyMoneySplit::Cleared:
rc = i18nc("Reconciliation status 'Cleared'", "cleared");
break;
case MyMoneySplit::Reconciled:
rc = i18nc("Reconciliation status 'Reconciled'", "reconciled");
break;
case MyMoneySplit::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/mymoneystoragesql.cpp b/kmymoney/mymoney/storage/mymoneystoragesql.cpp
index 831ecd80f..d23a44566 100644
--- a/kmymoney/mymoney/storage/mymoneystoragesql.cpp
+++ b/kmymoney/mymoney/storage/mymoneystoragesql.cpp
@@ -1,5004 +1,5005 @@
/***************************************************************************
mymoneystoragesql.cpp
---------------------
begin : 11 November 2005
copyright : (C) 2005 by Tony Bloomfield
email : tonybloom@users.sourceforge.net
: Fernando Vilas <fvilas@iname.com>
: Christian Dávid <christian-david@web.de>
***************************************************************************/
/***************************************************************************
* *
* 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.h"
// ----------------------------------------------------------------------------
// System Includes
#include <algorithm>
// ----------------------------------------------------------------------------
// QT Includes
#include <QString>
#include <QDateTime>
#include <QStringList>
#include <QIODevice>
#include <QUrlQuery>
#include <QSqlQuery>
#include <QSqlError>
#include <QList>
#include <QSqlRecord>
#include <QMap>
#include <QFile>
#include <QVariant>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// TODO: port KF5 (needed for payeeidentifier plugin)
//#include <KServiceTypeTrader>
// ----------------------------------------------------------------------------
// Project Includes
#include "imymoneystorage.h"
#include "imymoneyserialize.h"
#include "kmymoneystorageplugin.h"
#include "onlinejobadministration.h"
#include "payeeidentifier/payeeidentifierloader.h"
#include "onlinetasks/interfaces/tasks/onlinetask.h"
#include "mymoneycostcenter.h"
#include "mymoneyexception.h"
#include "mymoneymoney.h"
#include "mymoneyschedule.h"
#include "mymoneysplit.h"
#include "mymoneytransaction.h"
#include "mymoneyutils.h"
#include "payeeidentifier/payeeidentifierdata.h"
+#include "mymoneyenums.h"
using namespace eMyMoney;
// subclass QSqlQuery for performance tracing
MyMoneySqlQuery::MyMoneySqlQuery(MyMoneyStorageSql* db)
: QSqlQuery(*db)
{
}
MyMoneySqlQuery::~MyMoneySqlQuery()
{
}
bool MyMoneySqlQuery::exec()
{
qDebug() << "start sql:" << lastQuery();
bool rc = QSqlQuery::exec();
qDebug() << "end sql:" << QSqlQuery::executedQuery();
qDebug() << "***Query returned:" << rc << ", row count:" << numRowsAffected();
return (rc);
}
bool MyMoneySqlQuery::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 MyMoneySqlQuery::prepare(const QString & query)
{
return (QSqlQuery::prepare(query));
}
//*****************************************************************************
MyMoneyDbTransaction::MyMoneyDbTransaction(MyMoneyStorageSql& db, const QString& name)
: m_db(db), m_name(name)
{
db.startCommitUnit(name);
}
MyMoneyDbTransaction::~MyMoneyDbTransaction()
{
if (std::uncaught_exception()) {
m_db.cancelCommitUnit(m_name);
} else {
m_db.endCommitUnit(m_name);
}
}
//************************ Constructor/Destructor *****************************
MyMoneyStorageSql::MyMoneyStorageSql(IMyMoneySerialize *storage, const QUrl &url)
: QSqlDatabase(QUrlQuery(url).queryItemValue("driver")),
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_storage = storage;
m_storagePtr = dynamic_cast<IMyMoneyStorage*>(m_storage);
m_dbVersion = 0;
m_progressCallback = 0;
m_displayStatus = false;
m_readingPrices = false;
m_newDatabase = false;
m_loadAll = false;
m_override = false;
m_preferred.setReportAllSplits(false);
}
MyMoneyStorageSql::~MyMoneyStorageSql()
{
try {
close(true);
} catch (const MyMoneyException& e) {
qDebug() << "Caught Exception in MMStorageSql dtor: " << e.what();
}
}
int MyMoneyStorageSql::open(const QUrl &url, int openMode, bool clear)
{
try {
int rc = 0;
m_driver = MyMoneyDbDriver::create(QUrlQuery(url).queryItemValue("driver"));
//get the input options
QStringList options = QUrlQuery(url).queryItemValue("options").split(',');
m_loadAll = options.contains("loadAll")/*|| m_mode == 0*/;
m_override = options.contains("override");
// create the database connection
QString dbName = url.path();
setDatabaseName(dbName);
setHostName(url.host());
setUserName(url.userName());
setPassword(url.password());
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 (m_driver->requiresExternalFile()) {
if (!fileExists(dbName)) {
rc = 1;
break;
}
}
if (!QSqlDatabase::open()) {
buildError(QSqlQuery(*this), Q_FUNC_INFO, "opening database");
rc = 1;
} else {
rc = 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.
m_newDatabase = true;
if (!QSqlDatabase::open()) {
if (!createDatabase(url)) {
rc = 1;
} else {
if (!QSqlDatabase::open()) {
buildError(QSqlQuery(*this), Q_FUNC_INFO, "opening new database");
rc = 1;
} else {
rc = createTables();
}
}
} else {
rc = createTables();
if (rc == 0) {
if (clear) {
clean();
} else {
rc = 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 (m_newDatabase) return(0);
// check if the database is locked, if not lock it
readFileInfo();
if (!m_logonUser.isEmpty() && (!m_override)) {
m_error = i18n("Database apparently in use\nOpened by %1 on %2 at %3.\nOpen anyway?",
m_logonUser,
m_logonAt.date().toString(Qt::ISODate),
m_logonAt.time().toString("hh.mm.ss"));
qDebug("%s", qPrintable(m_error));
close(false);
rc = -1; // retryable error
} else {
m_logonUser = url.userName() + '@' + url.host();
m_logonAt = QDateTime::currentDateTime();
writeFileInfo();
}
return(rc);
} catch (const QString& s) {
qDebug("%s", qPrintable(s));
return (1);
}
}
void MyMoneyStorageSql::close(bool logoff)
{
if (QSqlDatabase::isOpen()) {
if (logoff) {
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
m_logonUser.clear();
writeFileInfo();
}
QSqlDatabase::close();
QSqlDatabase::removeDatabase(connectionName());
}
}
bool MyMoneyStorageSql::fileExists(const QString& dbName)
{
QFile f(dbName);
if (!f.exists()) {
m_error = i18n("SQLite file %1 does not exist", dbName);
return (false);
}
return (true);
}
bool MyMoneyStorageSql::createDatabase(const QUrl &url)
{
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", 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(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 MyMoneyStorageSql::upgradeDb()
{
//signalProgress(0, 1, QObject::tr("Upgrading database..."));
QSqlQuery q(*this);
q.prepare("SELECT version FROM kmmFileInfo;");
if (!q.exec() || !q.next()) { // krazy:exclude=crashy
if (!m_newDatabase) {
buildError(q, Q_FUNC_INFO, "Error retrieving file info (version)");
return(1);
} else {
m_dbVersion = m_db.currentVersion();
m_storage->setFileFixVersion(m_storage->currentFixVersion());
QSqlQuery q(*this);
q.prepare("UPDATE kmmFileInfo SET version = :version, \
fixLevel = :fixLevel;");
q.bindValue(":version", m_dbVersion);
q.bindValue(":fixLevel", m_storage->currentFixVersion());
if (!q.exec()) { // krazy:exclude=crashy
buildError(q, 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 = q.value(0).toString();
if (version.contains('.')) {
m_dbVersion = q.value(0).toString().section('.', 0, 0).toUInt();
m_storage->setFileFixVersion(q.value(0).toString().section('.', 1, 1).toUInt() - 1);
} else {
m_dbVersion = version.toUInt();
q.prepare("SELECT fixLevel FROM kmmFileInfo;");
if (!q.exec() || !q.next()) { // krazy:exclude=crashy
buildError(q, Q_FUNC_INFO, "Error retrieving file info (fixLevel)");
return(1);
}
m_storage->setFileFixVersion(q.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<QString, MyMoneyDbView>::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) {
if (lowerTables.contains(tt.key().toLower())) {
if (!q.exec("DROP VIEW " + tt.value().name() + ';')) // krazy:exclude=crashy
throw MYMONEYEXCEPTION(buildError(q, 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;
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<QString, MyMoneyDbView>::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) {
if (!lowerTables.contains(tt.key().toLower())) {
if (!q.exec(tt.value().createString())) // krazy:exclude=crashy
throw MYMONEYEXCEPTION(buildError(q, 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))
q.prepare(QString("UPDATE kmmFileInfo SET version = :version;"));
q.bindValue(":version", m_dbVersion);
if (!q.exec()) { // krazy:exclude=crashy
buildError(q, Q_FUNC_INFO, "Error updating db version");
return (1);
}
//signalProgress(-1,-1);
return (0);
}
int MyMoneyStorageSql::upgradeToV1()
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
// change kmmSplits pkey to (transactionId, splitId)
if (!q.exec("ALTER TABLE kmmSplits ADD PRIMARY KEY (transactionId, splitId);")) { // krazy:exclude=crashy
buildError(q, Q_FUNC_INFO, "Error updating kmmSplits pkey");
return (1);
}
// change kmmSplits alter checkNumber varchar(32)
if (!q.exec(m_db.m_tables["kmmSplits"].modifyColumnString(m_driver, "checkNumber", // krazy:exclude=crashy
MyMoneyDbColumn("checkNumber", "varchar(32)")))) {
buildError(q, 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)
q.prepare("SELECT id, postDate FROM kmmTransactions WHERE txType = 'N';");
if (!q.exec()) { // krazy:exclude=crashy
buildError(q, Q_FUNC_INFO, "Error priming kmmSplits.postDate");
return (1);
}
QMap<QString, QDateTime> tids;
while (q.next()) tids[q.value(0).toString()] = q.value(1).toDateTime();
QMap<QString, QDateTime>::ConstIterator it;
for (it = tids.constBegin(); it != tids.constEnd(); ++it) {
q.prepare("UPDATE kmmSplits SET postDate=:postDate WHERE transactionId = :id;");
q.bindValue(":postDate", it.value().toString(Qt::ISODate));
q.bindValue(":id", it.key());
if (!q.exec()) { // krazy:exclude=crashy
buildError(q, Q_FUNC_INFO, "priming kmmSplits.postDate");
return(1);
}
}
// add index to kmmKeyValuePairs to (kvpType,kvpId)
QStringList list;
list << "kvpType" << "kvpId";
if (!q.exec(MyMoneyDbIndex("kmmKeyValuePairs", "kmmKVPtype_id", list, false).generateDDL(m_driver) + ';')) {
buildError(q, Q_FUNC_INFO, "Error adding kmmKeyValuePairs index");
return (1);
}
// add index to kmmSplits to (accountId, txType)
list.clear();
list << "accountId" << "txType";
if (!q.exec(MyMoneyDbIndex("kmmSplits", "kmmSplitsaccount_type", list, false).generateDDL(m_driver) + ';')) {
buildError(q, Q_FUNC_INFO, "Error adding kmmSplits index");
return (1);
}
// change kmmSchedulePaymentHistory pkey to (schedId, payDate)
if (!q.exec("ALTER TABLE kmmSchedulePaymentHistory ADD PRIMARY KEY (schedId, payDate);")) {
buildError(q, Q_FUNC_INFO, "Error updating kmmSchedulePaymentHistory pkey");
return (1);
}
// change kmmPrices pkey to (fromId, toId, priceDate)
if (!q.exec("ALTER TABLE kmmPrices ADD PRIMARY KEY (fromId, toId, priceDate);")) {
buildError(q, 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 (!q.exec("ALTER TABLE kmmReportConfig ADD PRIMARY KEY (name);")) {
buildError(q, 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
q.prepare("SELECT id FROM kmmAccounts");
if (!q.exec()) { // krazy:exclude=crashy
buildError(q, Q_FUNC_INFO, "Error retrieving accounts for transaction counting");
return(1);
}
while (q.next()) {
m_transactionCountMap[q.value(0).toString()] = 0;
}
q.prepare("SELECT accountId, transactionId FROM kmmSplits WHERE txType = 'N' ORDER BY 1, 2");
if (!q.exec()) { // krazy:exclude=crashy
buildError(q, Q_FUNC_INFO, "Error retrieving splits for transaction counting");
return(1);
}
QString lastAcc, lastTx;
while (q.next()) {
QString thisAcc = q.value(0).toString();
QString thisTx = q.value(1).toString();
if ((thisAcc != lastAcc) || (thisTx != lastTx)) ++m_transactionCountMap[thisAcc];
lastAcc = thisAcc;
lastTx = thisTx;
}
QHash<QString, unsigned long>::ConstIterator itm;
q.prepare("UPDATE kmmAccounts SET transactionCount = :txCount WHERE id = :id;");
for (itm = m_transactionCountMap.constBegin(); itm != m_transactionCountMap.constEnd(); ++itm) {
q.bindValue(":txCount", QString::number(itm.value()));
q.bindValue(":id", itm.key());
if (!q.exec()) { // krazy:exclude=crashy
buildError(q, Q_FUNC_INFO, "Error updating transaction count");
return (1);
}
}
m_transactionCountMap.clear();
return (0);
}
int MyMoneyStorageSql::upgradeToV2()
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
// change kmmSplits add price, priceFormatted fields
if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion))
return (1);
return (0);
}
int MyMoneyStorageSql::upgradeToV3()
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
// kmmSchedules - add occurenceMultiplier
// The default value is given here to populate the column.
if (!q.exec("ALTER TABLE kmmSchedules ADD COLUMN " +
MyMoneyDbIntColumn("occurenceMultiplier",
MyMoneyDbIntColumn::SMALL, false, false, true)
.generateDDL(m_driver) + " DEFAULT 0;")) {
buildError(q, 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 MyMoneyStorageSql::upgradeToV4()
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
// kmmSplits - add index on transactionId + splitId
QStringList list;
list << "transactionId" << "splitId";
if (!q.exec(MyMoneyDbIndex("kmmSplits", "kmmTx_Split", list, false).generateDDL(m_driver) + ';')) {
buildError(q, Q_FUNC_INFO, "Error adding kmmSplits index on (transactionId, splitId)");
return (1);
}
return 0;
}
int MyMoneyStorageSql::upgradeToV5()
{
MyMoneyDbTransaction dbtrans(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
// 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 MyMoneyStorageSql::upgradeToV6()
{
startCommitUnit(Q_FUNC_INFO);
QSqlQuery q(*this);
// 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<QString, MyMoneyDbTable>::ConstIterator tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) {
if (!q.exec(QString("ALTER TABLE %1 ENGINE = InnoDB;").arg(tt.value().name()))) {
buildError(q, 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 (!q.exec("ALTER TABLE kmmReportConfig ADD COLUMN " +
MyMoneyDbColumn("id", "varchar(32)").generateDDL(m_driver) + ';')) {
buildError(q, Q_FUNC_INFO, "adding id to report table");
return(1);
}
QMap<QString, MyMoneyReport> reportList = 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 (!q.exec("DELETE FROM kmmReportConfig;")) {
buildError(q, 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<QString, MyMoneyReport>::const_iterator it_r;
for (it_r = reportList.constBegin(); it_r != reportList.constEnd(); ++it_r) {
MyMoneyReport r = *it_r;
q.prepare(m_db.m_tables["kmmReportConfig"].insertString());
writeReport(*it_r, q);
}
m_storage->loadReportId(m_hiIdReports);
endCommitUnit(Q_FUNC_INFO);
return 0;
}
int MyMoneyStorageSql::upgradeToV7()
{
MyMoneyDbTransaction dbtrans(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
// 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 MyMoneyStorageSql::upgradeToV8()
{
MyMoneyDbTransaction dbtrans(*this, Q_FUNC_INFO);
// Added onlineJobs and payeeIdentifier
if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion))
return (1);
return 0;
}
int MyMoneyStorageSql::upgradeToV9()
{
MyMoneyDbTransaction dbtrans(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
// kmmSplits - add bankId
if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion))
return (1);
return 0;
}
int MyMoneyStorageSql::upgradeToV10()
{
MyMoneyDbTransaction dbtrans(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
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 MyMoneyStorageSql::upgradeToV11()
{
MyMoneyDbTransaction dbtrans(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
// 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;
}
bool MyMoneyStorageSql::alterTable(const MyMoneyDbTable& t, int fromVersion)
{
const int toVersion = fromVersion + 1;
QString tempTableName = t.name();
tempTableName.replace("kmm", "kmmtmp");
QSqlQuery q(*this);
// 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 (!q.exec(dropString)) {
buildError(q, 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 (!q.exec(m_driver->dropIndexString(t.name(), indexName))) {
buildError(q, Q_FUNC_INFO, QString("Error dropping index from %1").arg(t.name()));
return false;
}
}
if (!q.exec(QString("ALTER TABLE " + t.name() + " RENAME TO " + tempTableName + ';'))) {
buildError(q, Q_FUNC_INFO, QString("Error renaming table %1").arg(t.name()));
return false;
}
createTable(t, toVersion);
if (getRecCount(tempTableName) > 0) {
q.prepare(QString("INSERT INTO " + t.name() + " (" + t.columnList(fromVersion) +
") SELECT " + t.columnList(fromVersion) + " FROM " + tempTableName + ';'));
if (!q.exec()) { // krazy:exclude=crashy
buildError(q, Q_FUNC_INFO, QString("Error inserting into new table %1").arg(t.name()));
return false;
}
}
if (!q.exec(QString("DROP TABLE " + tempTableName + ';'))) {
buildError(q, Q_FUNC_INFO, QString("Error dropping old table %1").arg(t.name()));
return false;
}
return true;
}
long unsigned MyMoneyStorageSql::getRecCount(const QString& table) const
{
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(this));
q.prepare(QString("SELECT COUNT(*) FROM %1;").arg(table));
if ((!q.exec()) || (!q.next())) { // krazy:exclude=crashy
buildError(q, Q_FUNC_INFO, "error retrieving record count");
qFatal("Error retrieving record count"); // definitely shouldn't happen
}
return ((unsigned long) q.value(0).toULongLong());
}
int MyMoneyStorageSql::createTables()
{
// 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<QString, MyMoneyDbTable>::ConstIterator tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) {
if (!lowerTables.contains(tt.key().toLower())) {
createTable(tt.value());
}
}
QSqlQuery q(*this);
for (QMap<QString, MyMoneyDbView>::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) {
if (!lowerTables.contains(tt.key().toLower())) {
if (!q.exec(tt.value().createString())) throw MYMONEYEXCEPTION(buildError(q, 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) {
q.prepare(QLatin1String("INSERT INTO kmmFileInfo (version, fixLevel) VALUES(?,?);"));
q.bindValue(0, m_dbVersion);
q.bindValue(1, m_storage->fileFixVersion());
if (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("Saving database version")));
}
return upgradeDb();
}
void MyMoneyStorageSql::createTable(const MyMoneyDbTable& t, int version)
{
// create the tables
QStringList ql = t.generateCreateSQL(m_driver, version).split('\n', QString::SkipEmptyParts);
QSqlQuery q(*this);
foreach (const QString& i, ql) {
if (!q.exec(i)) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("creating table/index %1").arg(t.name())));
}
}
int MyMoneyStorageSql::isEmpty()
{
// check all tables are empty
QMap<QString, MyMoneyDbTable>::ConstIterator tt = m_db.tableBegin();
int recordCount = 0;
QSqlQuery q(*this);
while ((tt != m_db.tableEnd()) && (recordCount == 0)) {
q.prepare(QString("select count(*) from %1;").arg((*tt).name()));
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "getting record count")); // krazy:exclude=crashy
if (!q.next()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "retrieving record count"));
recordCount += q.value(0).toInt();
++tt;
}
if (recordCount != 0) {
return (-1); // not empty
} else {
return (0);
}
}
void MyMoneyStorageSql::clean()
{
// delete all existing records
QMap<QString, MyMoneyDbTable>::ConstIterator it = m_db.tableBegin();
QSqlQuery q(*this);
while (it != m_db.tableEnd()) {
q.prepare(QString("DELETE from %1;").arg(it.key()));
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("cleaning database"))); // krazy:exclude=crashy
++it;
}
}
//////////////////////////////////////////////////////////////////
bool MyMoneyStorageSql::readFile()
{
m_displayStatus = true;
try {
readFileInfo();
readInstitutions();
if (m_loadAll) {
readPayees();
} else {
QList<QString> user;
user.append(QString("USER"));
readPayees(user);
}
readTags();
readCurrencies();
readSecurities();
readAccounts();
if (m_loadAll) {
readTransactions();
} else {
if (m_preferred.filterSet().singleFilter.accountFilter) readTransactions(m_preferred);
}
readSchedules();
readPrices();
readReports();
readBudgets();
//FIXME - ?? if (m_mode == 0)
//m_storage->rebuildAccountBalances();
// 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
signalProgress(-1, -1);
m_displayStatus = false;
//MyMoneySqlQuery::traceOn();
return true;
} catch (const QString &) {
return false;
}
}
// The following is called from 'SaveAsDatabase'
bool MyMoneyStorageSql::writeFile()
{
// initialize record counts and hi ids
m_institutions = m_accounts = m_payees = m_tags = m_transactions = m_splits
= m_securities = m_prices = m_currencies = m_schedules = m_reports = m_kvps = m_budgets = 0;
m_hiIdInstitutions = m_hiIdPayees = m_hiIdTags = m_hiIdAccounts = m_hiIdTransactions =
m_hiIdSchedules = m_hiIdSecurities = m_hiIdReports = m_hiIdBudgets = 0;
m_onlineJobs = m_payeeIdentifier = 0;
m_displayStatus = true;
try {
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
writeInstitutions();
writePayees();
writeTags();
writeAccounts();
writeTransactions();
writeSchedules();
writeSecurities();
writePrices();
writeCurrencies();
writeReports();
writeBudgets();
writeOnlineJobs();
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
signalProgress(-1, -1);
m_displayStatus = false;
return true;
} catch (const QString &) {
return false;
}
}
long unsigned MyMoneyStorageSql::highestNumberFromIdString(QString tableName, QString tableField, int prefixLength)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
if (!q.exec(m_driver->highestNumberFromIdString(tableName, tableField, prefixLength)) || !q.next())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("retrieving highest ID number"))); // krazy:exclude=crashy
return q.value(0).toULongLong();
}
// --------------- SQL Transaction (commit unit) handling -----------------------------------
void MyMoneyStorageSql::startCommitUnit(const QString& callingFunction)
{
if (m_commitUnitStack.isEmpty()) {
if (!transaction()) throw MYMONEYEXCEPTION(buildError(QSqlQuery(), callingFunction, "starting commit unit") + ' ' + callingFunction);
}
m_commitUnitStack.push(callingFunction);
}
bool MyMoneyStorageSql::endCommitUnit(const QString& callingFunction)
{
// 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 (m_commitUnitStack.isEmpty()) {
throw MYMONEYEXCEPTION("Empty commit unit stack while trying to commit");
}
if (callingFunction != m_commitUnitStack.top())
qDebug("%s", qPrintable(QString("%1 - %2 s/be %3").arg(Q_FUNC_INFO).arg(callingFunction).arg(m_commitUnitStack.top())));
m_commitUnitStack.pop();
if (m_commitUnitStack.isEmpty()) {
//qDebug() << "Committing with " << QSqlQuery::refCount() << " queries";
if (!commit()) throw MYMONEYEXCEPTION(buildError(QSqlQuery(), callingFunction, "ending commit unit"));
}
return rc;
}
void MyMoneyStorageSql::cancelCommitUnit(const QString& callingFunction)
{
if (m_commitUnitStack.isEmpty()) return;
if (callingFunction != m_commitUnitStack.top())
qDebug("%s", qPrintable(QString("%1 - %2 s/be %3").arg(Q_FUNC_INFO).arg(callingFunction).arg(m_commitUnitStack.top())));
m_commitUnitStack.clear();
if (!rollback()) throw MYMONEYEXCEPTION(buildError(QSqlQuery(), callingFunction, "cancelling commit unit") + ' ' + callingFunction);
}
/////////////////////////////////////////////////////////////////////
void MyMoneyStorageSql::fillStorage()
{
// if (!m_transactionListRead) // make sure we have loaded everything
readTransactions();
// if (!m_payeeListRead)
readPayees();
}
//------------------------------ Write SQL routines ----------------------------------------
// **** Institutions ****
void MyMoneyStorageSql::writeInstitutions()
{
// 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<QString> dbList;
QSqlQuery q(*this);
q.prepare("SELECT id FROM kmmInstitutions;");
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "building Institution list")); // krazy:exclude=crashy
while (q.next()) dbList.append(q.value(0).toString());
const QList<MyMoneyInstitution> list = m_storage->institutionList();
QList<MyMoneyInstitution> insertList;
QList<MyMoneyInstitution> updateList;
QSqlQuery q2(*this);
q.prepare(m_db.m_tables["kmmInstitutions"].updateString());
q2.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, q2);
if (!updateList.isEmpty())
writeInstitutionList(updateList, q);
if (!dbList.isEmpty()) {
QVariantList deleteList;
// qCopy segfaults here, so do it with a hand-rolled loop
foreach (const QString& it, dbList) {
deleteList << it;
}
q.prepare("DELETE FROM kmmInstitutions WHERE id = :id");
q.bindValue(":id", deleteList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Institution"));
deleteKeyValuePairs("OFXSETTINGS", deleteList);
}
}
void MyMoneyStorageSql::addInstitution(const MyMoneyInstitution& inst)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmInstitutions"].insertString());
QList<MyMoneyInstitution> iList;
iList << inst;
writeInstitutionList(iList , q);
++m_institutions;
writeFileInfo();
}
void MyMoneyStorageSql::modifyInstitution(const MyMoneyInstitution& inst)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmInstitutions"].updateString());
QVariantList kvpList;
kvpList << inst.id();
deleteKeyValuePairs("OFXSETTINGS", kvpList);
QList<MyMoneyInstitution> iList;
iList << inst;
writeInstitutionList(iList , q);
writeFileInfo();
}
void MyMoneyStorageSql::removeInstitution(const MyMoneyInstitution& inst)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QVariantList kvpList;
kvpList << inst.id();
deleteKeyValuePairs("OFXSETTINGS", kvpList);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmInstitutions"].deleteString());
q.bindValue(":id", inst.id());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting Institution"))); // krazy:exclude=crashy
--m_institutions;
writeFileInfo();
}
void MyMoneyStorageSql::writeInstitutionList(const QList<MyMoneyInstitution>& iList, QSqlQuery& q)
{
QVariantList idList;
QVariantList nameList;
QVariantList managerList;
QVariantList routingCodeList;
QVariantList addressStreetList;
QVariantList addressCityList;
QVariantList addressZipcodeList;
QVariantList telephoneList;
QList<QMap<QString, QString> > 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();
}
q.bindValue(":id", idList);
q.bindValue(":name", nameList);
q.bindValue(":manager", managerList);
q.bindValue(":routingCode", routingCodeList);
q.bindValue(":addressStreet", addressStreetList);
q.bindValue(":addressCity", addressCityList);
q.bindValue(":addressZipcode", addressZipcodeList);
q.bindValue(":telephone", telephoneList);
if (!q.execBatch())
throw MYMONEYEXCEPTION(buildError(q, 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 MyMoneyStorageSql::writePayees()
{
// first, get a list of what's on the database (see writeInstitutions)
QSqlQuery q(*this);
q.prepare("SELECT id FROM kmmPayees;");
if (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "building Payee list")); // krazy:exclude=crashy
QList<QString> dbList;
dbList.reserve(q.numRowsAffected());
while (q.next())
dbList.append(q.value(0).toString());
QList<MyMoneyPayee> 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());
modifyPayee(it);
} else {
addPayee(it);
}
signalProgress(++m_payees, 0);
}
if (!dbList.isEmpty()) {
QMap<QString, MyMoneyPayee> payeesToDelete = fetchPayees(dbList, true);
Q_FOREACH(const MyMoneyPayee& payee, payeesToDelete) {
removePayee(payee);
}
}
}
void MyMoneyStorageSql::addPayee(const MyMoneyPayee& payee)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmPayees"].insertString());
writePayee(payee, q);
++m_payees;
QVariantList identIds;
QList<payeeIdentifier> 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(buildError(q, Q_FUNC_INFO, QString("writing payee's identifiers"))); // krazy:exclude=crashy
}
writeFileInfo();
}
void MyMoneyStorageSql::modifyPayee(MyMoneyPayee payee)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmPayees"].updateString());
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(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(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<payeeIdentifier> idents(payee.payeeIdentifiers());
QVariantList order;
QVariantList payeeIdList;
QVariantList identIdList;
order.reserve(idents.size());
payeeIdList.reserve(idents.size());
identIdList.reserve(idents.size());
{
QList<payeeIdentifier>::const_iterator end = idents.constEnd();
int i = 0;
for (QList<payeeIdentifier>::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(buildError(q, Q_FUNC_INFO, QString("writing payee's identifiers during modify"))); // krazy:exclude=crashy
writeFileInfo();
}
void MyMoneyStorageSql::modifyUserInfo(const MyMoneyPayee& payee)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmPayees"].updateString());
writePayee(payee, q, true);
writeFileInfo();
}
void MyMoneyStorageSql::removePayee(const MyMoneyPayee& payee)
{
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(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<QString, payeeIdentifier> 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(buildError(q, Q_FUNC_INFO, QString("removing payee's identifiers (delete from mapping table)"))); // krazy:exclude=crashy
// Delete payee
q.prepare(m_db.m_tables["kmmPayees"].deleteString());
q.bindValue(":id", payee.id());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting Payee"))); // krazy:exclude=crashy
--m_payees;
writeFileInfo();
}
void MyMoneyStorageSql::writePayee(const MyMoneyPayee& p, QSqlQuery& q, bool isUserInfo)
{
if (isUserInfo) {
q.bindValue(":id", "USER");
} else {
q.bindValue(":id", p.id());
}
q.bindValue(":name", p.name());
q.bindValue(":reference", p.reference());
q.bindValue(":email", p.email());
q.bindValue(":addressStreet", p.address());
q.bindValue(":addressCity", p.city());
q.bindValue(":addressZipcode", p.postcode());
q.bindValue(":addressState", p.state());
q.bindValue(":telephone", p.telephone());
q.bindValue(":notes", p.notes());
q.bindValue(":defaultAccountId", p.defaultAccountId());
bool ignoreCase;
QString matchKeys;
MyMoneyPayee::payeeMatchType type = p.matchData(ignoreCase, matchKeys);
q.bindValue(":matchData", static_cast<unsigned int>(type));
if (ignoreCase)
q.bindValue(":matchIgnoreCase", "Y");
else
q.bindValue(":matchIgnoreCase", "N");
q.bindValue(":matchKeys", matchKeys);
if (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Payee"))); // krazy:exclude=crashy
if (!isUserInfo)
m_hiIdPayees = 0;
}
// **** Tags ****
void MyMoneyStorageSql::writeTags()
{
// first, get a list of what's on the database (see writeInstitutions)
QList<QString> dbList;
QSqlQuery q(*this);
q.prepare("SELECT id FROM kmmTags;");
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "building Tag list")); // krazy:exclude=crashy
while (q.next()) dbList.append(q.value(0).toString());
QList<MyMoneyTag> list = m_storage->tagList();
signalProgress(0, list.count(), "Writing Tags...");
QSqlQuery q2(*this);
q.prepare(m_db.m_tables["kmmTags"].updateString());
q2.prepare(m_db.m_tables["kmmTags"].insertString());
foreach (const MyMoneyTag& it, list) {
if (dbList.contains(it.id())) {
dbList.removeAll(it.id());
writeTag(it, q);
} else {
writeTag(it, q2);
}
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;
}
q.prepare(m_db.m_tables["kmmTags"].deleteString());
q.bindValue(":id", deleteList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Tag"));
m_tags -= q.numRowsAffected();
}
}
void MyMoneyStorageSql::addTag(const MyMoneyTag& tag)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmTags"].insertString());
writeTag(tag, q);
++m_tags;
writeFileInfo();
}
void MyMoneyStorageSql::modifyTag(const MyMoneyTag& tag)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmTags"].updateString());
writeTag(tag, q);
writeFileInfo();
}
void MyMoneyStorageSql::removeTag(const MyMoneyTag& tag)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmTags"].deleteString());
q.bindValue(":id", tag.id());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting Tag"))); // krazy:exclude=crashy
--m_tags;
writeFileInfo();
}
void MyMoneyStorageSql::writeTag(const MyMoneyTag& ta, QSqlQuery& q)
{
q.bindValue(":id", ta.id());
q.bindValue(":name", ta.name());
q.bindValue(":tagColor", ta.tagColor().name());
if (ta.isClosed()) q.bindValue(":closed", "Y");
else q.bindValue(":closed", "N");
q.bindValue(":notes", ta.notes());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Tag"))); // krazy:exclude=crashy
m_hiIdTags = 0;
}
// **** Accounts ****
void MyMoneyStorageSql::writeAccounts()
{
// first, get a list of what's on the database (see writeInstitutions)
QList<QString> dbList;
QSqlQuery q(*this);
q.prepare("SELECT id FROM kmmAccounts;");
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "building Account list")); // krazy:exclude=crashy
while (q.next()) dbList.append(q.value(0).toString());
QList<MyMoneyAccount> list;
m_storage->accountList(list);
unsigned progress = 0;
signalProgress(0, list.count(), "Writing Accounts...");
if (dbList.isEmpty()) { // new table, insert standard accounts
q.prepare(m_db.m_tables["kmmAccounts"].insertString());
} else {
q.prepare(m_db.m_tables["kmmAccounts"].updateString());
}
// Attempt to write the standard accounts. For an empty db, this will fail.
try {
QList<MyMoneyAccount> stdList;
stdList << m_storage->asset();
stdList << m_storage->liability();
stdList << m_storage->expense();
stdList << m_storage->income();
stdList << m_storage->equity();
writeAccountList(stdList, q);
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(MyMoneyAccount::Liability);
+ acc_l.setAccountType(eMyMoney::Account::Liability);
acc_l.setName("Liability");
MyMoneyAccount liability(STD_ACC_LIABILITY, acc_l);
MyMoneyAccount acc_a;
- acc_a.setAccountType(MyMoneyAccount::Asset);
+ acc_a.setAccountType(eMyMoney::Account::Asset);
acc_a.setName("Asset");
MyMoneyAccount asset(STD_ACC_ASSET, acc_a);
MyMoneyAccount acc_e;
- acc_e.setAccountType(MyMoneyAccount::Expense);
+ acc_e.setAccountType(eMyMoney::Account::Expense);
acc_e.setName("Expense");
MyMoneyAccount expense(STD_ACC_EXPENSE, acc_e);
MyMoneyAccount acc_i;
- acc_i.setAccountType(MyMoneyAccount::Income);
+ acc_i.setAccountType(eMyMoney::Account::Income);
acc_i.setName("Income");
MyMoneyAccount income(STD_ACC_INCOME, acc_i);
MyMoneyAccount acc_q;
- acc_q.setAccountType(MyMoneyAccount::Equity);
+ acc_q.setAccountType(eMyMoney::Account::Equity);
acc_q.setName("Equity");
MyMoneyAccount equity(STD_ACC_EQUITY, acc_q);
QList<MyMoneyAccount> stdList;
stdList << asset;
stdList << liability;
stdList << expense;
stdList << income;
stdList << equity;
writeAccountList(stdList, q);
m_accounts += stdList.size();
}
QSqlQuery q2(*this);
q.prepare(m_db.m_tables["kmmAccounts"].updateString());
q2.prepare(m_db.m_tables["kmmAccounts"].insertString());
QList<MyMoneyAccount> updateList;
QList<MyMoneyAccount> 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());
if (dbList.contains(it.id())) {
dbList.removeAll(it.id());
updateList << it;
} else {
insertList << it;
}
signalProgress(++progress, 0);
++m_accounts;
}
writeAccountList(updateList, q);
writeAccountList(insertList, q2);
// Delete the accounts that are in the db but no longer in memory.
if (!dbList.isEmpty()) {
QVariantList kvpList;
q.prepare("DELETE FROM kmmAccounts WHERE id = :id");
foreach (const QString& it, dbList) {
if (!m_storagePtr->isStandardAccount(it)) {
kvpList << it;
}
}
q.bindValue(":id", kvpList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Account"));
deleteKeyValuePairs("ACCOUNT", kvpList);
deleteKeyValuePairs("ONLINEBANKING", kvpList);
}
}
void MyMoneyStorageSql::addAccount(const MyMoneyAccount& acc)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmAccounts"].insertString());
QList<MyMoneyAccount> aList;
aList << acc;
writeAccountList(aList, q);
++m_accounts;
writeFileInfo();
}
void MyMoneyStorageSql::modifyAccount(const MyMoneyAccount& acc)
{
QList<MyMoneyAccount> aList;
aList << acc;
modifyAccountList(aList);
}
void MyMoneyStorageSql::modifyAccountList(const QList<MyMoneyAccount>& acc)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmAccounts"].updateString());
QVariantList kvpList;
foreach (const MyMoneyAccount& a, acc) {
kvpList << a.id();
}
deleteKeyValuePairs("ACCOUNT", kvpList);
deleteKeyValuePairs("ONLINEBANKING", kvpList);
writeAccountList(acc, q);
writeFileInfo();
}
void MyMoneyStorageSql::removeAccount(const MyMoneyAccount& acc)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QVariantList kvpList;
kvpList << acc.id();
deleteKeyValuePairs("ACCOUNT", kvpList);
deleteKeyValuePairs("ONLINEBANKING", kvpList);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmAccounts"].deleteString());
q.bindValue(":id", acc.id());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting Account"))); // krazy:exclude=crashy
--m_accounts;
writeFileInfo();
}
void MyMoneyStorageSql::writeAccountList(const QList<MyMoneyAccount>& accList, QSqlQuery& q)
{
//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<QMap<QString, QString> > pairs;
QList<QMap<QString, QString> > 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 << a.accountType();
+ accountTypeList << (int)a.accountType();
accountTypeStringList << MyMoneyAccount::accountTypeToString(a.accountType());
- if (a.accountType() == MyMoneyAccount::Stock)
+ if (a.accountType() == eMyMoney::Account::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());
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();
}
q.bindValue(":id", idList);
q.bindValue(":institutionId", institutionIdList);
q.bindValue(":parentId", parentIdList);
q.bindValue(":lastReconciled", lastReconciledList);
q.bindValue(":lastModified", lastModifiedList);
q.bindValue(":openingDate", openingDateList);
q.bindValue(":accountNumber", accountNumberList);
q.bindValue(":accountType", accountTypeList);
q.bindValue(":accountTypeString", accountTypeStringList);
q.bindValue(":isStockAccount", isStockAccountList);
q.bindValue(":accountName", accountNameList);
q.bindValue(":description", descriptionList);
q.bindValue(":currencyId", currencyIdList);
q.bindValue(":balance", balanceList);
q.bindValue(":balanceFormatted", balanceFormattedList);
q.bindValue(":transactionCount", transactionCountList);
if (!q.execBatch())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Account")));
//Add in Key-Value Pairs for accounts.
writeKeyValuePairs("ACCOUNT", idList, pairs);
writeKeyValuePairs("ONLINEBANKING", idList, onlineBankingPairs);
m_hiIdAccounts = 0;
}
// **** Transactions and Splits ****
void MyMoneyStorageSql::writeTransactions()
{
// first, get a list of what's on the database (see writeInstitutions)
QList<QString> dbList;
QSqlQuery q(*this);
q.prepare("SELECT id FROM kmmTransactions WHERE txType = 'N';");
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "building Transaction list")); // krazy:exclude=crashy
while (q.next()) dbList.append(q.value(0).toString());
MyMoneyTransactionFilter filter;
filter.setReportAllSplits(false);
QList<MyMoneyTransaction> list;
m_storage->transactionList(list, filter);
signalProgress(0, list.count(), "Writing Transactions...");
QList<MyMoneyTransaction>::ConstIterator it;
QSqlQuery q2(*this);
q.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, q, "N");
} else {
writeTransaction(it.id(), it, q2, "N");
}
signalProgress(++m_transactions, 0);
}
if (!dbList.isEmpty()) {
foreach (const QString& it, dbList) {
deleteTransaction(it);
}
}
}
void MyMoneyStorageSql::addTransaction(const MyMoneyTransaction& tx)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
// add the transaction and splits
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmTransactions"].insertString());
writeTransaction(tx.id(), tx, q, "N");
++m_transactions;
QList<MyMoneyAccount> aList;
// for each split account, update lastMod date, balance, txCount
foreach (const MyMoneySplit& it_s, tx.splits()) {
MyMoneyAccount acc = m_storagePtr->account(it_s.accountId());
++m_transactionCountMap[acc.id()];
aList << acc;
}
modifyAccountList(aList);
// in the fileinfo record, update lastMod, txCount, next TxId
writeFileInfo();
}
void MyMoneyStorageSql::modifyTransaction(const MyMoneyTransaction& tx)
{
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(buildError(q, Q_FUNC_INFO, "retrieving old splits"));
while (q.next()) {
QString id = q.value(0).toString();
--m_transactionCountMap[id];
}
// add the transaction and splits
q.prepare(m_db.m_tables["kmmTransactions"].updateString());
writeTransaction(tx.id(), tx, q, "N");
QList<MyMoneyAccount> aList;
// for each split account, update lastMod date, balance, txCount
foreach (const MyMoneySplit& it_s, tx.splits()) {
MyMoneyAccount acc = m_storagePtr->account(it_s.accountId());
++m_transactionCountMap[acc.id()];
aList << acc;
}
modifyAccountList(aList);
//writeSplits(tx.id(), "N", tx.splits());
// in the fileinfo record, update lastMod
writeFileInfo();
}
void MyMoneyStorageSql::removeTransaction(const MyMoneyTransaction& tx)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
deleteTransaction(tx.id());
--m_transactions;
QList<MyMoneyAccount> aList;
// for each split account, update lastMod date, balance, txCount
foreach (const MyMoneySplit& it_s, tx.splits()) {
MyMoneyAccount acc = m_storagePtr->account(it_s.accountId());
--m_transactionCountMap[acc.id()];
aList << acc;
}
modifyAccountList(aList);
// in the fileinfo record, update lastModDate, txCount
writeFileInfo();
}
void MyMoneyStorageSql::deleteTransaction(const QString& id)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
QVariantList idList;
idList << id;
q.prepare("DELETE FROM kmmSplits WHERE transactionId = :transactionId;");
q.bindValue(":transactionId", idList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Splits"));
q.prepare("DELETE FROM kmmKeyValuePairs WHERE kvpType = 'SPLIT' "
"AND kvpId LIKE '?%'");
q.bindValue(1, idList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Splits KVP"));
m_splits -= q.numRowsAffected();
deleteKeyValuePairs("TRANSACTION", idList);
q.prepare(m_db.m_tables["kmmTransactions"].deleteString());
q.bindValue(":id", idList);
if (!q.execBatch())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Transaction"));
}
void MyMoneyStorageSql::writeTransaction(const QString& txId, const MyMoneyTransaction& tx, QSqlQuery& q, const QString& type)
{
q.bindValue(":id", txId);
q.bindValue(":txType", type);
q.bindValue(":postDate", tx.postDate().toString(Qt::ISODate));
q.bindValue(":memo", tx.memo());
q.bindValue(":entryDate", tx.entryDate().toString(Qt::ISODate));
q.bindValue(":currencyId", tx.commodity());
q.bindValue(":bankId", tx.bankID());
if (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Transaction"))); // krazy:exclude=crashy
m_txPostDate = tx.postDate(); // FIXME: TEMP till Tom puts date in split object
QList<MyMoneySplit> splitList = tx.splits();
writeSplits(txId, type, splitList);
//Add in Key-Value Pairs for transactions.
QVariantList idList;
idList << txId;
deleteKeyValuePairs("TRANSACTION", idList);
QList<QMap<QString, QString> > pairs;
pairs << tx.pairs();
writeKeyValuePairs("TRANSACTION", idList, pairs);
m_hiIdTransactions = 0;
}
void MyMoneyStorageSql::writeSplits(const QString& txId, const QString& type, const QList<MyMoneySplit>& splitList)
{
// first, get a list of what's on the database (see writeInstitutions)
QList<unsigned int> dbList;
QList<MyMoneySplit> insertList;
QList<MyMoneySplit> updateList;
QList<int> insertIdList;
QList<int> updateIdList;
QSqlQuery q(*this);
q.prepare("SELECT splitId FROM kmmSplits where transactionId = :id;");
q.bindValue(":id", txId);
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "building Split list")); // krazy:exclude=crashy
while (q.next()) dbList.append(q.value(0).toUInt());
QList<MyMoneySplit>::ConstIterator it;
unsigned int i = 0;
QSqlQuery q2(*this);
q.prepare(m_db.m_tables["kmmSplits"].updateString());
q2.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, q2);
writeTagSplitsList(txId, insertList, insertIdList);
}
if (!updateList.isEmpty()) {
writeSplitList(txId, updateList, type, updateIdList, q);
deleteTagSplitsList(txId, updateIdList);
writeTagSplitsList(txId, updateList, updateIdList);
}
if (!dbList.isEmpty()) {
QVector<QVariant> txIdList(dbList.count(), txId);
QVariantList splitIdList;
q.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;
}
q.bindValue(":txId", txIdList.toList());
q.bindValue(":splitId", splitIdList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Splits"));
}
}
void MyMoneyStorageSql::deleteTagSplitsList(const QString& txId, const QList<int>& splitIdList)
{
MyMoneyDbTransaction t(*this, 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 q(*this);
q.prepare("DELETE FROM kmmTagSplits WHERE transactionId = :transactionId AND splitId = :splitId");
q.bindValue(":splitId", iList);
q.bindValue(":transactionId", transactionIdList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting tagSplits")));
}
void MyMoneyStorageSql::writeTagSplitsList
(const QString& txId,
const QList<MyMoneySplit>& splitList,
const QList<int>& splitIdList)
{
MyMoneyDbTransaction t(*this, 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 q(*this);
q.prepare(m_db.m_tables["kmmTagSplits"].insertString());
q.bindValue(":tagId", tagIdList);
q.bindValue(":splitId", splitIdList_TagSplits);
q.bindValue(":transactionId", txIdList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing tagSplits")));
}
void MyMoneyStorageSql::writeSplitList
(const QString& txId,
const QList<MyMoneySplit>& splitList,
const QString& type,
const QList<int>& splitIdList,
QSqlQuery& q)
{
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<QMap<QString, QString> > 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 << 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());
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;
}
q.bindValue(":transactionId", txIdList);
q.bindValue(":txType", typeList);
QVariantList iList;
// qCopy segfaults here, so do it with a hand-rolled loop
foreach (int it_s, splitIdList) {
iList << it_s;
}
q.bindValue(":splitId", iList);
q.bindValue(":payeeId", payeeIdList);
q.bindValue(":reconcileDate", reconcileDateList);
q.bindValue(":action", actionList);
q.bindValue(":reconcileFlag", reconcileFlagList);
q.bindValue(":value", valueList);
q.bindValue(":valueFormatted", valueFormattedList);
q.bindValue(":shares", sharesList);
q.bindValue(":sharesFormatted", sharesFormattedList);
q.bindValue(":price", priceList);
q.bindValue(":priceFormatted", priceFormattedList);
q.bindValue(":memo", memoList);
q.bindValue(":accountId", accountIdList);
q.bindValue(":costCenterId", costCenterIdList);
q.bindValue(":checkNumber", checkNumberList);
q.bindValue(":postDate", postDateList);
q.bindValue(":bankId", bankIdList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Split")));
deleteKeyValuePairs("SPLIT", kvpIdList);
writeKeyValuePairs("SPLIT", kvpIdList, kvpPairsList);
}
// **** Schedules ****
void MyMoneyStorageSql::writeSchedules()
{
// first, get a list of what's on the database (see writeInstitutions)
QList<QString> dbList;
QSqlQuery q(*this);
q.prepare("SELECT id FROM kmmSchedules;");
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "building Schedule list")); // krazy:exclude=crashy
while (q.next()) dbList.append(q.value(0).toString());
const QList<MyMoneySchedule> list = m_storage->scheduleList();
QSqlQuery q2(*this);
//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) {
q.prepare(m_db.m_tables["kmmSchedules"].updateString());
q2.prepare(m_db.m_tables["kmmSchedules"].insertString());
bool insert = true;
if (dbList.contains(it.id())) {
dbList.removeAll(it.id());
insert = false;
writeSchedule(it, q, insert);
} else {
writeSchedule(it, q2, insert);
}
signalProgress(++m_schedules, 0);
}
if (!dbList.isEmpty()) {
foreach (const QString& it, dbList) {
deleteSchedule(it);
}
}
}
void MyMoneyStorageSql::addSchedule(const MyMoneySchedule& sched)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmSchedules"].insertString());
writeSchedule(sched, q, true);
++m_schedules;
writeFileInfo();
}
void MyMoneyStorageSql::modifySchedule(const MyMoneySchedule& sched)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmSchedules"].updateString());
writeSchedule(sched, q, false);
writeFileInfo();
}
void MyMoneyStorageSql::removeSchedule(const MyMoneySchedule& sched)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
deleteSchedule(sched.id());
--m_schedules;
writeFileInfo();
}
void MyMoneyStorageSql::deleteSchedule(const QString& id)
{
deleteTransaction(id);
QSqlQuery q(*this);
q.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id");
q.bindValue(":id", id);
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Schedule Payment History")); // krazy:exclude=crashy
q.prepare(m_db.m_tables["kmmSchedules"].deleteString());
q.bindValue(":id", id);
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Schedule")); // krazy:exclude=crashy
//FIXME: enable when schedules have KVPs.
//deleteKeyValuePairs("SCHEDULE", id);
}
void MyMoneyStorageSql::writeSchedule(const MyMoneySchedule& sch, QSqlQuery& q, bool insert)
{
q.bindValue(":id", sch.id());
q.bindValue(":name", sch.name());
- q.bindValue(":type", sch.type());
+ q.bindValue(":type", (int)sch.type());
q.bindValue(":typeString", MyMoneySchedule::scheduleTypeToString(sch.type()));
- q.bindValue(":occurence", sch.occurrencePeriod()); // krazy:exclude=spelling
+ q.bindValue(":occurence", (int)sch.occurrencePeriod()); // krazy:exclude=spelling
q.bindValue(":occurenceMultiplier", sch.occurrenceMultiplier());
q.bindValue(":occurenceString", sch.occurrenceToString());
- q.bindValue(":paymentType", sch.paymentType());
+ q.bindValue(":paymentType", (int)sch.paymentType());
q.bindValue(":paymentTypeString", MyMoneySchedule::paymentMethodToString(sch.paymentType()));
q.bindValue(":startDate", sch.startDate().toString(Qt::ISODate));
q.bindValue(":endDate", sch.endDate().toString(Qt::ISODate));
if (sch.isFixed()) {
q.bindValue(":fixed", "Y");
} else {
q.bindValue(":fixed", "N");
}
if (sch.autoEnter()) {
q.bindValue(":autoEnter", "Y");
} else {
q.bindValue(":autoEnter", "N");
}
q.bindValue(":lastPayment", sch.lastPayment());
q.bindValue(":nextPaymentDue", sch.nextDueDate().toString(Qt::ISODate));
- q.bindValue(":weekendOption", sch.weekendOption());
+ q.bindValue(":weekendOption", (int)sch.weekendOption());
q.bindValue(":weekendOptionString", MyMoneySchedule::weekendOptionToString(sch.weekendOption()));
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, 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
q.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id;");
q.bindValue(":id", sch.id());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting Schedule Payment History"))); // krazy:exclude=crashy
q.prepare(m_db.m_tables["kmmSchedulePaymentHistory"].insertString());
foreach (const QDate& it, sch.recordedPayments()) {
q.bindValue(":schedId", sch.id());
q.bindValue(":payDate", it.toString(Qt::ISODate));
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Schedule Payment History"))); // krazy:exclude=crashy
}
//store the transaction data for this task.
if (!insert) {
q.prepare(m_db.m_tables["kmmTransactions"].updateString());
} else {
q.prepare(m_db.m_tables["kmmTransactions"].insertString());
}
writeTransaction(sch.id(), sch.transaction(), q, "S");
//FIXME: enable when schedules have KVPs.
//Add in Key-Value Pairs for transactions.
//deleteKeyValuePairs("SCHEDULE", sch.id());
//writeKeyValuePairs("SCHEDULE", sch.id(), sch.pairs());
}
// **** Securities ****
void MyMoneyStorageSql::writeSecurities()
{
// first, get a list of what's on the database (see writeInstitutions)
QList<QString> dbList;
QSqlQuery q(*this);
QSqlQuery q2(*this);
q.prepare("SELECT id FROM kmmSecurities;");
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "building security list")); // krazy:exclude=crashy
while (q.next()) dbList.append(q.value(0).toString());
const QList<MyMoneySecurity> securityList = m_storage->securityList();
signalProgress(0, securityList.count(), "Writing Securities...");
q.prepare(m_db.m_tables["kmmSecurities"].updateString());
q2.prepare(m_db.m_tables["kmmSecurities"].insertString());
foreach (const MyMoneySecurity& it, securityList) {
if (dbList.contains(it.id())) {
dbList.removeAll(it.id());
writeSecurity(it, q);
} else {
writeSecurity(it, q2);
}
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;
}
q.prepare("DELETE FROM kmmSecurities WHERE id = :id");
q2.prepare("DELETE FROM kmmPrices WHERE fromId = :id OR toId = :id");
q.bindValue(":id", idList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Security"));
q2.bindValue(":fromId", idList);
q2.bindValue(":toId", idList);
if (!q2.execBatch()) throw MYMONEYEXCEPTION(buildError(q2, Q_FUNC_INFO, "deleting Security"));
deleteKeyValuePairs("SECURITY", idList);
}
}
void MyMoneyStorageSql::addSecurity(const MyMoneySecurity& sec)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmSecurities"].insertString());
writeSecurity(sec, q);
++m_securities;
writeFileInfo();
}
void MyMoneyStorageSql::modifySecurity(const MyMoneySecurity& sec)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QVariantList kvpList;
kvpList << sec.id();
deleteKeyValuePairs("SECURITY", kvpList);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmSecurities"].updateString());
writeSecurity(sec, q);
writeFileInfo();
}
void MyMoneyStorageSql::removeSecurity(const MyMoneySecurity& sec)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QVariantList kvpList;
kvpList << sec.id();
deleteKeyValuePairs("SECURITY", kvpList);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmSecurities"].deleteString());
q.bindValue(":id", kvpList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting Security")));
--m_securities;
writeFileInfo();
}
void MyMoneyStorageSql::writeSecurity(const MyMoneySecurity& security, QSqlQuery& q)
{
q.bindValue(":id", security.id());
q.bindValue(":name", security.name());
q.bindValue(":symbol", security.tradingSymbol());
q.bindValue(":type", static_cast<int>(security.securityType()));
q.bindValue(":typeString", MyMoneySecurity::securityTypeToString(security.securityType()));
q.bindValue(":roundingMethod", static_cast<int>(security.roundingMethod()));
q.bindValue(":smallestAccountFraction", security.smallestAccountFraction());
q.bindValue(":pricePrecision", security.pricePrecision());
q.bindValue(":tradingCurrency", security.tradingCurrency());
q.bindValue(":tradingMarket", security.tradingMarket());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Securities"))); // krazy:exclude=crashy
//Add in Key-Value Pairs for security
QVariantList idList;
idList << security.id();
QList<QMap<QString, QString> > pairs;
pairs << security.pairs();
writeKeyValuePairs("SECURITY", idList, pairs);
m_hiIdSecurities = 0;
}
// **** Prices ****
void MyMoneyStorageSql::writePrices()
{
// due to difficulties in matching and determining deletes
// easiest way is to delete all and re-insert
QSqlQuery q(*this);
q.prepare("DELETE FROM kmmPrices");
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, 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 MyMoneyStorageSql::writePricePair(const MyMoneyPriceEntries& p)
{
MyMoneyPriceEntries::ConstIterator it;
for (it = p.constBegin(); it != p.constEnd(); ++it) {
writePrice(*it);
signalProgress(++m_prices, 0);
}
}
void MyMoneyStorageSql::addPrice(const MyMoneyPrice& p)
{
if (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 = 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(buildError(q, Q_FUNC_INFO, QString("finding Price"))); // krazy:exclude=crashy
if (q.next()) {
q.prepare(m_db.m_tables["kmmPrices"].updateString());
} else {
q.prepare(m_db.m_tables["kmmPrices"].insertString());
++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 = m_storagePtr->security(p.to());
q.bindValue(":priceFormatted",
p.rate(QString()).formatMoney("", sec.pricePrecision()));
q.bindValue(":priceSource", p.source());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Price"))); // krazy:exclude=crashy
if (newRecord) writeFileInfo();
}
void MyMoneyStorageSql::removePrice(const MyMoneyPrice& p)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(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(buildError(q, Q_FUNC_INFO, QString("deleting Price"))); // krazy:exclude=crashy
--m_prices;
writeFileInfo();
}
void MyMoneyStorageSql::writePrice(const MyMoneyPrice& p)
{
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmPrices"].insertString());
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());
q.bindValue(":priceFormatted", p.rate(QString()).formatMoney("", 2));
q.bindValue(":priceSource", p.source());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Prices"))); // krazy:exclude=crashy
}
// **** Currencies ****
void MyMoneyStorageSql::writeCurrencies()
{
// first, get a list of what's on the database (see writeInstitutions)
QList<QString> dbList;
QSqlQuery q(*this);
QSqlQuery q2(*this);
q.prepare("SELECT ISOCode FROM kmmCurrencies;");
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "building Currency list")); // krazy:exclude=crashy
while (q.next()) dbList.append(q.value(0).toString());
const QList<MyMoneySecurity> currencyList = m_storage->currencyList();
signalProgress(0, currencyList.count(), "Writing Currencies...");
q.prepare(m_db.m_tables["kmmCurrencies"].updateString());
q2.prepare(m_db.m_tables["kmmCurrencies"].insertString());
foreach (const MyMoneySecurity& it, currencyList) {
if (dbList.contains(it.id())) {
dbList.removeAll(it.id());
writeCurrency(it, q);
} else {
writeCurrency(it, q2);
}
signalProgress(++m_currencies, 0);
}
if (!dbList.isEmpty()) {
QVariantList isoCodeList;
q.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;
}
q.bindValue(":ISOCode", isoCodeList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Currency"));
}
}
void MyMoneyStorageSql::addCurrency(const MyMoneySecurity& sec)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmCurrencies"].insertString());
writeCurrency(sec, q);
++m_currencies;
writeFileInfo();
}
void MyMoneyStorageSql::modifyCurrency(const MyMoneySecurity& sec)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmCurrencies"].updateString());
writeCurrency(sec, q);
writeFileInfo();
}
void MyMoneyStorageSql::removeCurrency(const MyMoneySecurity& sec)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmCurrencies"].deleteString());
q.bindValue(":ISOcode", sec.id());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting Currency"))); // krazy:exclude=crashy
--m_currencies;
writeFileInfo();
}
void MyMoneyStorageSql::writeCurrency(const MyMoneySecurity& currency, QSqlQuery& q)
{
q.bindValue(":ISOcode", currency.id());
q.bindValue(":name", currency.name());
q.bindValue(":type", static_cast<int>(currency.securityType()));
q.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());
q.bindValue(":symbol1", symutf[0]);
q.bindValue(":symbol2", symutf[1]);
q.bindValue(":symbol3", symutf[2]);
q.bindValue(":symbolString", symbol);
q.bindValue(":smallestCashFraction", currency.smallestCashFraction());
q.bindValue(":smallestAccountFraction", currency.smallestAccountFraction());
q.bindValue(":pricePrecision", currency.pricePrecision());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Currencies"))); // krazy:exclude=crashy
}
void MyMoneyStorageSql::writeReports()
{
// first, get a list of what's on the database (see writeInstitutions)
QList<QString> dbList;
QSqlQuery q(*this);
QSqlQuery q2(*this);
q.prepare("SELECT id FROM kmmReportConfig;");
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "building Report list")); // krazy:exclude=crashy
while (q.next()) dbList.append(q.value(0).toString());
QList<MyMoneyReport> list = m_storage->reportList();
signalProgress(0, list.count(), "Writing Reports...");
q.prepare(m_db.m_tables["kmmReportConfig"].updateString());
q2.prepare(m_db.m_tables["kmmReportConfig"].insertString());
foreach (const MyMoneyReport& it, list) {
if (dbList.contains(it.id())) {
dbList.removeAll(it.id());
writeReport(it, q);
} else {
writeReport(it, q2);
}
signalProgress(++m_reports, 0);
}
if (!dbList.isEmpty()) {
QVariantList idList;
q.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;
}
q.bindValue(":id", idList);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Report"));
}
}
void MyMoneyStorageSql::addReport(const MyMoneyReport& rep)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmReportConfig"].insertString());
writeReport(rep, q);
++m_reports;
writeFileInfo();
}
void MyMoneyStorageSql::modifyReport(const MyMoneyReport& rep)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmReportConfig"].updateString());
writeReport(rep, q);
writeFileInfo();
}
void MyMoneyStorageSql::removeReport(const MyMoneyReport& rep)
{
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(buildError(q, Q_FUNC_INFO, QString("deleting Report"))); // krazy:exclude=crashy
--m_reports;
writeFileInfo();
}
void MyMoneyStorageSql::writeReport(const MyMoneyReport& rep, QSqlQuery& q)
{
QDomDocument d; // create a dummy XML document
QDomElement e = d.createElement("REPORTS");
d.appendChild(e);
rep.writeXML(d, e); // write the XML to document
q.bindValue(":id", rep.id());
q.bindValue(":name", rep.name());
q.bindValue(":XML", d.toString());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Reports"))); // krazy:exclude=crashy
}
void MyMoneyStorageSql::writeBudgets()
{
// first, get a list of what's on the database (see writeInstitutions)
QList<QString> dbList;
QSqlQuery q(*this);
QSqlQuery q2(*this);
q.prepare("SELECT name FROM kmmBudgetConfig;");
if (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "building Budget list")); // krazy:exclude=crashy
while (q.next())
dbList.append(q.value(0).toString());
QList<MyMoneyBudget> list = m_storage->budgetList();
signalProgress(0, list.count(), "Writing Budgets...");
q.prepare(m_db.m_tables["kmmBudgetConfig"].updateString());
q2.prepare(m_db.m_tables["kmmBudgetConfig"].insertString());
foreach (const MyMoneyBudget& it, list) {
if (dbList.contains(it.name())) {
dbList.removeAll(it.name());
writeBudget(it, q);
} else {
writeBudget(it, q2);
}
signalProgress(++m_budgets, 0);
}
if (!dbList.isEmpty()) {
QVariantList idList;
q.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;
}
q.bindValue(":name", idList);
if (!q.execBatch())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "deleting Budget"));
}
}
void MyMoneyStorageSql::addBudget(const MyMoneyBudget& bud)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmBudgetConfig"].insertString());
writeBudget(bud, q);
++m_budgets;
writeFileInfo();
}
void MyMoneyStorageSql::modifyBudget(const MyMoneyBudget& bud)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmBudgetConfig"].updateString());
writeBudget(bud, q);
writeFileInfo();
}
void MyMoneyStorageSql::removeBudget(const MyMoneyBudget& bud)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmBudgetConfig"].deleteString());
q.bindValue(":id", bud.id());
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting Budget"))); // krazy:exclude=crashy
--m_budgets;
writeFileInfo();
}
void MyMoneyStorageSql::writeBudget(const MyMoneyBudget& bud, QSqlQuery& q)
{
QDomDocument d; // create a dummy XML document
QDomElement e = d.createElement("BUDGETS");
d.appendChild(e);
bud.writeXML(d, e); // write the XML to document
q.bindValue(":id", bud.id());
q.bindValue(":name", bud.name());
q.bindValue(":start", bud.budgetStart());
q.bindValue(":XML", d.toString());
if (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing Budgets"))); // krazy:exclude=crashy
}
bool MyMoneyStorageSql::setupStoragePlugin(QString iid)
{
// 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<KMyMoneyPlugin::storagePlugin>(
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(*this, Q_FUNC_INFO);
if (plugin->setupDatabase(*this)) {
m_loadedStoragePlugins.insert(iid);
return true;
}
throw MYMONEYEXCEPTION(QString("Could not install sqlStoragePlugin '%1' in database.").arg(iid));
}
void MyMoneyStorageSql::insertStorableObject(const databaseStoreableObject& obj, const QString& id)
{
setupStoragePlugin(obj.storagePluginIid());
if (!obj.sqlSave(*this, id))
throw MYMONEYEXCEPTION(QString("Could not save object with id '%1' in database (plugin failed).").arg(id));
}
void MyMoneyStorageSql::updateStorableObject(const databaseStoreableObject& obj, const QString& id)
{
setupStoragePlugin(obj.storagePluginIid());
if (!obj.sqlModify(*this, id))
throw MYMONEYEXCEPTION(QString("Could not modify object with id '%1' in database (plugin failed).").arg(id));
}
void MyMoneyStorageSql::deleteStorableObject(const databaseStoreableObject& obj, const QString& id)
{
setupStoragePlugin(obj.storagePluginIid());
if (!obj.sqlRemove(*this, id))
throw MYMONEYEXCEPTION(QString("Could not remove object with id '%1' from database (plugin failed).").arg(id));
}
void MyMoneyStorageSql::addOnlineJob(const onlineJob& job)
{
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);");
writeOnlineJob(job, q);
if (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing onlineJob"))); // krazy:exclude=crashy
++m_onlineJobs;
try {
// Save online task
insertStorableObject(*job.constTask(), job.id());
} catch (onlineJob::emptyTask&) {
}
}
void MyMoneyStorageSql::modifyOnlineJob(const onlineJob& job)
{
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"
));
writeOnlineJob(job, query);
if (!query.exec())
throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing onlineJob"))); // krazy:exclude=crashy
try {
// Modify online task
updateStorableObject(*job.constTask(), job.id());
} catch (onlineJob::emptyTask&) {
// If there is no task attached this is fine as well
}
}
void MyMoneyStorageSql::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<QString>(job.isLocked() ? QLatin1String("Y") : QLatin1String("N")));
}
void MyMoneyStorageSql::writeOnlineJobs()
{
QSqlQuery query(*this);
if (!query.exec("DELETE FROM kmmOnlineJobs;"))
throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QLatin1String("Clean kmmOnlineJobs table")));
const QList<onlineJob> jobs(m_storage->onlineJobList());
signalProgress(0, jobs.count(), i18n("Inserting online jobs."));
// Create list for onlineJobs which failed and the reason therefor
QList<QPair<onlineJob, QString> > failedJobs;
int jobCount = 0;
foreach (const onlineJob& job, jobs) {
try {
addOnlineJob(job);
} catch (MyMoneyException& e) {
// Do not save e as this may point to an inherited class
failedJobs.append(QPair<onlineJob, QString>(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()));
}
}
void MyMoneyStorageSql::removeOnlineJob(const onlineJob& job)
{
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
deleteStorableObject(*job.constTask(), job.id());
} catch (onlineJob::emptyTask&) {
}
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmOnlineJobs"].deleteString());
q.bindValue(":id", job.id());
if (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting onlineJob"))); // krazy:exclude=crashy
--m_onlineJobs;
}
void MyMoneyStorageSql::addPayeeIdentifier(payeeIdentifier& ident)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
ident = payeeIdentifier(incrementPayeeIdentfierId(), ident);
QSqlQuery q(*this);
q.prepare("INSERT INTO kmmPayeeIdentifier (id, type) VALUES(:id, :type)");
writePayeeIdentifier(ident, q);
++m_payeeIdentifier;
try {
insertStorableObject(*ident.data(), ident.idString());
} catch (payeeIdentifier::empty&) {
}
}
void MyMoneyStorageSql::modifyPayeeIdentifier(const payeeIdentifier& ident)
{
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(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 {
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");
writePayeeIdentifier(ident, q);
try {
if (typeChanged)
insertStorableObject(*ident.data(), ident.idString());
else
updateStorableObject(*ident.data(), ident.idString());
} catch (payeeIdentifier::empty&) {
}
}
void MyMoneyStorageSql::removePayeeIdentifier(const payeeIdentifier& ident)
{
MyMoneyDbTransaction t(*this, Q_FUNC_INFO);
// Remove first, the table could have a contraint which prevents removal
// of row in kmmPayeeIdentifier
try {
deleteStorableObject(*ident.data(), ident.idString());
} catch (payeeIdentifier::empty&) {
}
QSqlQuery q(*this);
q.prepare(m_db.m_tables["kmmPayeeIdentifier"].deleteString());
q.bindValue(":id", ident.idString());
if (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting payeeIdentifier"))); // krazy:exclude=crashy
--m_payeeIdentifier;
}
void MyMoneyStorageSql::writePayeeIdentifier(const payeeIdentifier& pid, QSqlQuery& query)
{
query.bindValue(":id", pid.idString());
query.bindValue(":type", pid.iid());
if (!query.exec()) {
qWarning() << buildError(query, Q_FUNC_INFO, QString("modifying payeeIdentifier"));
throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("modifying payeeIdentifier"))); // krazy:exclude=crashy
}
}
void MyMoneyStorageSql::writeFileInfo()
{
// we have no real way of knowing when these change, so re-write them every time
QVariantList kvpList;
kvpList << "";
QList<QMap<QString, QString> > pairs;
pairs << m_storage->pairs();
deleteKeyValuePairs("STORAGE", kvpList);
writeKeyValuePairs("STORAGE", kvpList, pairs);
QSqlQuery q(*this);
q.prepare("SELECT count(*) FROM kmmFileInfo;");
if (!q.exec() || !q.next())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "checking fileinfo")); // krazy:exclude=crashy
if (q.value(0).toInt() == 0) {
// Cannot use "INSERT INTO kmmFileInfo DEFAULT VALUES;" because it is not supported by MySQL
q.prepare(QLatin1String("INSERT INTO kmmFileInfo (version) VALUES (null);"));
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, "inserting fileinfo")); // krazy:exclude=crashy
}
q.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; "
)
);
q.bindValue(":version", m_dbVersion);
q.bindValue(":fixLevel", m_storage->fileFixVersion());
q.bindValue(":created", m_storage->creationDate().toString(Qt::ISODate));
//q.bindValue(":lastModified", m_storage->lastModificationDate().toString(Qt::ISODate));
q.bindValue(":lastModified", QDate::currentDate().toString(Qt::ISODate));
q.bindValue(":baseCurrency", m_storage->pairs()["kmm-baseCurrency"]);
q.bindValue(":dateRangeStart", QDate());
q.bindValue(":dateRangeEnd", QDate());
//FIXME: This modifies all m_<variable> 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 (...) {
//startCommitUnit(Q_FUNC_INFO);
//}
//! @todo The following bindings are for backwards compatibility only
//! remove backwards compatibility in a later version
q.bindValue(":hiInstitutionId", QVariant::fromValue(getNextInstitutionId()));
q.bindValue(":hiPayeeId", QVariant::fromValue(getNextPayeeId()));
q.bindValue(":hiTagId", QVariant::fromValue(getNextTagId()));
q.bindValue(":hiAccountId", QVariant::fromValue(getNextAccountId()));
q.bindValue(":hiTransactionId", QVariant::fromValue(getNextTransactionId()));
q.bindValue(":hiScheduleId", QVariant::fromValue(getNextScheduleId()));
q.bindValue(":hiSecurityId", QVariant::fromValue(getNextSecurityId()));
q.bindValue(":hiReportId", QVariant::fromValue(getNextReportId()));
q.bindValue(":hiBudgetId", QVariant::fromValue(getNextBudgetId()));
q.bindValue(":hiOnlineJobId", QVariant::fromValue(getNextOnlineJobId()));
q.bindValue(":hiPayeeIdentifierId", QVariant::fromValue(getNextPayeeIdentifierId()));
q.bindValue(":encryptData", m_encryptData);
q.bindValue(":updateInProgress", "N");
q.bindValue(":logonUser", m_logonUser);
q.bindValue(":logonAt", m_logonAt.toString(Qt::ISODate));
//! @todo The following bindings are for backwards compatibility only
//! remove backwards compatibility in a later version
q.bindValue(":institutions", (unsigned long long) m_institutions);
q.bindValue(":accounts", (unsigned long long) m_accounts);
q.bindValue(":payees", (unsigned long long) m_payees);
q.bindValue(":tags", (unsigned long long) m_tags);
q.bindValue(":transactions", (unsigned long long) m_transactions);
q.bindValue(":splits", (unsigned long long) m_splits);
q.bindValue(":securities", (unsigned long long) m_securities);
q.bindValue(":prices", (unsigned long long) m_prices);
q.bindValue(":currencies", (unsigned long long) m_currencies);
q.bindValue(":schedules", (unsigned long long) m_schedules);
q.bindValue(":reports", (unsigned long long) m_reports);
q.bindValue(":kvps", (unsigned long long) m_kvps);
q.bindValue(":budgets", (unsigned long long) m_budgets);
if (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing FileInfo"))); // krazy:exclude=crashy
}
// **** Key/value pairs ****
void MyMoneyStorageSql::writeKeyValuePairs(const QString& kvpType, const QVariantList& kvpId, const QList<QMap<QString, QString> >& pairs)
{
if (pairs.empty())
return;
QVariantList type;
QVariantList id;
QVariantList key;
QVariantList value;
int pairCount = 0;
for (int i = 0; i < kvpId.size(); ++i) {
QMap<QString, QString>::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 q(*this);
q.prepare(m_db.m_tables["kmmKeyValuePairs"].insertString());
q.bindValue(":kvpType", type);
q.bindValue(":kvpId", id);
q.bindValue(":kvpKey", key);
q.bindValue(":kvpData", value);
if (!q.execBatch()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("writing KVP")));
m_kvps += pairCount;
}
void MyMoneyStorageSql::deleteKeyValuePairs(const QString& kvpType, const QVariantList& idList)
{
QSqlQuery q(*this);
q.prepare("DELETE FROM kmmKeyValuePairs WHERE kvpType = :kvpType AND kvpId = :kvpId;");
QVariantList typeList;
for (int i = 0; i < idList.size(); ++i) {
typeList << kvpType;
}
q.bindValue(":kvpType", typeList);
q.bindValue(":kvpId", idList);
if (!q.execBatch()) {
QString idString;
for (int i = 0; i < idList.size(); ++i) {
idString.append(idList[i].toString() + ' ');
}
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("deleting kvp for %1 %2").arg(kvpType).arg(idString)));
}
m_kvps -= q.numRowsAffected();
}
//******************************** read SQL routines **************************************
#define GETSTRING(a) q.value(a).toString()
#define GETDATE(a) getDate(GETSTRING(a))
#define GETDATETIME(a) getDateTime(GETSTRING(a))
#define GETINT(a) q.value(a).toInt()
#define GETULL(a) q.value(a).toULongLong()
void MyMoneyStorageSql::readFileInfo()
{
signalProgress(0, 1, QObject::tr("Loading file information..."));
QSqlQuery q(*this);
q.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 (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("reading FileInfo"))); // krazy:exclude=crashy
if (!q.next())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("retrieving FileInfo")));
QSqlRecord rec = q.record();
m_storage->setCreationDate(GETDATE(rec.indexOf("created")));
m_storage->setLastModificationDate(GETDATE(rec.indexOf("lastModified")));
m_institutions = (unsigned long) GETULL(rec.indexOf("institutions"));
m_accounts = (unsigned long) GETULL(rec.indexOf("accounts"));
m_payees = (unsigned long) GETULL(rec.indexOf("payees"));
m_tags = (unsigned long) GETULL(rec.indexOf("tags"));
m_transactions = (unsigned long) GETULL(rec.indexOf("transactions"));
m_splits = (unsigned long) GETULL(rec.indexOf("splits"));
m_securities = (unsigned long) GETULL(rec.indexOf("securities"));
m_currencies = (unsigned long) GETULL(rec.indexOf("currencies"));
m_schedules = (unsigned long) GETULL(rec.indexOf("schedules"));
m_prices = (unsigned long) GETULL(rec.indexOf("prices"));
m_kvps = (unsigned long) GETULL(rec.indexOf("kvps"));
m_reports = (unsigned long) GETULL(rec.indexOf("reports"));
m_budgets = (unsigned long) GETULL(rec.indexOf("budgets"));
m_onlineJobs = (unsigned long) GETULL(rec.indexOf("onlineJobs"));
m_payeeIdentifier = (unsigned long) 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 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);
}*/
void MyMoneyStorageSql::readInstitutions()
{
try {
QMap<QString, MyMoneyInstitution> iList = fetchInstitutions();
m_storage->loadInstitutions(iList);
readFileInfo();
m_storage->loadInstitutionId(m_hiIdInstitutions);
} catch (const MyMoneyException &) {
throw;
}
}
const QMap<QString, MyMoneyInstitution> MyMoneyStorageSql::fetchInstitutions(const QStringList& idList, bool forUpdate) const
{
int institutionsNb = (idList.isEmpty() ? m_institutions : idList.size());
signalProgress(0, institutionsNb, QObject::tr("Loading institutions..."));
int progress = 0;
QMap<QString, MyMoneyInstitution> iList;
unsigned long lastId = 0;
const MyMoneyDbTable& t = m_db.m_tables["kmmInstitutions"];
QSqlQuery sq(*const_cast <MyMoneyStorageSql*>(this));
sq.prepare("SELECT id from kmmAccounts where institutionId = :id");
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(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 += m_driver->forUpdateString();
queryString += ';';
q.prepare(queryString);
if (! idList.empty()) {
QStringList::ConstIterator bindVal = idList.constBegin();
for (int i = 0; bindVal != idList.constEnd(); ++i, ++bindVal) {
q.bindValue(QString(":id%1").arg(i), *bindVal);
}
}
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, 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 (q.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(buildError(q, 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);
unsigned long id = extractId(iid);
if (id > lastId)
lastId = id;
signalProgress(++progress, 0);
}
return iList;
}
void MyMoneyStorageSql::readPayees(const QString& id)
{
QList<QString> list;
list.append(id);
readPayees(list);
}
void MyMoneyStorageSql::readPayees(const QList<QString>& pid)
{
try {
m_storage->loadPayees(fetchPayees(pid));
m_storage->loadPayeeId(getNextPayeeId());
} catch (const MyMoneyException &) {
}
// if (pid.isEmpty()) m_payeeListRead = true;
}
const QMap<QString, MyMoneyPayee> MyMoneyStorageSql::fetchPayees(const QStringList& idList, bool /*forUpdate*/) const
{
MyMoneyDbTransaction trans(const_cast <MyMoneyStorageSql&>(*this), Q_FUNC_INFO);
if (m_displayStatus) {
int payeesNb = (idList.isEmpty() ? m_payees : idList.size());
signalProgress(0, payeesNb, QObject::tr("Loading payees..."));
}
int progress = 0;
QMap<QString, MyMoneyPayee> pList;
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(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;");
q.prepare(queryString);
if (!idList.isEmpty()) {
// Bind values
QStringList::const_iterator end = idList.constEnd();
for (QStringList::const_iterator iter = idList.constBegin(); iter != end; ++iter) {
q.addBindValue(*iter);
}
}
if (!q.exec())
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("reading Payee"))); // krazy:exclude=crashy
const QSqlRecord record = q.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");
q.next();
while (q.isValid()) {
QString pid;
QString boolChar;
MyMoneyPayee payee;
unsigned int 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<MyMoneyPayee::payeeMatchType>(type), ignoreCase, matchKeys);
// Get payeeIdentifier ids
QStringList identifierIds;
do {
identifierIds.append(GETSTRING(identIdCol));
} while (q.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")
m_storage->setUser(payee);
else
pList[pid] = MyMoneyPayee(pid, payee);
if (m_displayStatus)
signalProgress(++progress, 0);
}
return pList;
}
void MyMoneyStorageSql::readTags(const QString& id)
{
QList<QString> list;
list.append(id);
readTags(list);
}
void MyMoneyStorageSql::readTags(const QList<QString>& pid)
{
try {
m_storage->loadTags(fetchTags(pid));
readFileInfo();
m_storage->loadTagId(m_hiIdTags);
} catch (const MyMoneyException &) {
}
// if (pid.isEmpty()) m_tagListRead = true;
}
const QMap<QString, onlineJob> MyMoneyStorageSql::fetchOnlineJobs(const QStringList& idList, bool forUpdate) const
{
Q_UNUSED(forUpdate);
MyMoneyDbTransaction trans(const_cast <MyMoneyStorageSql&>(*this), Q_FUNC_INFO);
if (m_displayStatus)
signalProgress(0, idList.isEmpty() ? m_onlineJobs : idList.size(), QObject::tr("Loading online banking data..."));
// Create query
QSqlQuery query(*const_cast <MyMoneyStorageSql*>(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(buildError(query, Q_FUNC_INFO, QString("reading onlineJobs"))); // krazy:exclude=crashy
// Create onlineJobs
int progress = 0;
QMap<QString, onlineJob> 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 (m_displayStatus)
signalProgress(++progress, 0);
}
return jobList;
}
payeeIdentifier MyMoneyStorageSql::fetchPayeeIdentifier(const QString& id) const
{
QMap<QString, payeeIdentifier> list = fetchPayeeIdentifiers(QStringList(id));
QMap<QString, payeeIdentifier>::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;
}
const QMap< QString, payeeIdentifier > MyMoneyStorageSql::fetchPayeeIdentifiers(const QStringList& idList) const
{
MyMoneyDbTransaction trans(const_cast <MyMoneyStorageSql&>(*this), Q_FUNC_INFO);
// Create query
QSqlQuery query(*const_cast <MyMoneyStorageSql*>(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(buildError(query, Q_FUNC_INFO, QString("reading payee identifiers"))); // krazy:exclude=crashy
QMap<QString, payeeIdentifier> 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;
}
const QMap<QString, MyMoneyTag> MyMoneyStorageSql::fetchTags(const QStringList& idList, bool /*forUpdate*/) const
{
MyMoneyDbTransaction trans(const_cast <MyMoneyStorageSql&>(*this), Q_FUNC_INFO);
if (m_displayStatus) {
int tagsNb = (idList.isEmpty() ? m_tags : idList.size());
signalProgress(0, tagsNb, QObject::tr("Loading tags..."));
} else {
// if (m_tagListRead) return;
}
int progress = 0;
QMap<QString, MyMoneyTag> taList;
//unsigned long lastId;
const MyMoneyDbTable& t = m_db.m_tables["kmmTags"];
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(this));
if (idList.isEmpty()) {
q.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 += ')';
q.prepare(t.selectAllString(false) + whereClause);
}
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, 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 (q.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 (m_displayStatus) signalProgress(++progress, 0);
}
return taList;
}
const QMap<QString, MyMoneyAccount> MyMoneyStorageSql::fetchAccounts(const QStringList& idList, bool forUpdate) const
{
int accountsNb = (idList.isEmpty() ? m_accounts : idList.size());
signalProgress(0, accountsNb, QObject::tr("Loading accounts..."));
int progress = 0;
QMap<QString, MyMoneyAccount> accList;
QStringList kvpAccountList(idList);
const MyMoneyDbTable& t = m_db.m_tables["kmmAccounts"];
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(this));
QSqlQuery sq(*const_cast <MyMoneyStorageSql*>(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 += m_driver->forUpdateString();
childQueryString += m_driver->forUpdateString();
}
q.prepare(queryString);
sq.prepare(childQueryString);
if (! idList.empty()) {
QStringList::ConstIterator bindVal = idList.constBegin();
for (int i = 0; bindVal != idList.constEnd(); ++i, ++bindVal) {
q.bindValue(QString(":id%1").arg(i), *bindVal);
sq.bindValue(QString(":id%1").arg(i), *bindVal);
}
}
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("reading Account"))); // krazy:exclude=crashy
if (!sq.exec()) throw MYMONEYEXCEPTION(buildError(q, 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 (q.next()) {
QString aid;
QString balance;
MyMoneyAccount acc;
aid = GETSTRING(idCol);
acc.setInstitutionId(GETSTRING(institutionIdCol));
acc.setParentAccountId(GETSTRING(parentIdCol));
acc.setLastReconciliationDate(GETDATE(lastReconciledCol));
acc.setLastModified(GETDATE(lastModifiedCol));
acc.setOpeningDate(GETDATE(openingDateCol));
acc.setNumber(GETSTRING(accountNumberCol));
- acc.setAccountType(static_cast<MyMoneyAccount::accountTypeE>(GETINT(accountTypeCol)));
+ acc.setAccountType(static_cast<eMyMoney::Account>(GETINT(accountTypeCol)));
acc.setName(GETSTRING(accountNameCol));
acc.setDescription(GETSTRING(descriptionCol));
acc.setCurrencyId(GETSTRING(currencyIdCol));
acc.setBalance(MyMoneyMoney(GETSTRING(balanceCol)));
const_cast <MyMoneyStorageSql*>(this)->m_transactionCountMap[aid] = (unsigned long) 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 <MyMoneyStorageSql*>(this)->m_preferred.addAccount(aid);
}
signalProgress(++progress, 0);
}
QMap<QString, MyMoneyAccount>::Iterator it_acc;
QMap<QString, MyMoneyAccount>::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 <QString, MyMoneyKeyValueContainer> kvpResult = readKeyValuePairs("ACCOUNT", kvpAccountList);
QHash <QString, MyMoneyKeyValueContainer>::const_iterator kvp_end = kvpResult.constEnd();
for (QHash <QString, MyMoneyKeyValueContainer>::const_iterator it_kvp = kvpResult.constBegin();
it_kvp != kvp_end; ++it_kvp) {
accList[it_kvp.key()].setPairs(it_kvp.value().pairs());
}
kvpResult = readKeyValuePairs("ONLINEBANKING", kvpAccountList);
kvp_end = kvpResult.constEnd();
for (QHash <QString, MyMoneyKeyValueContainer>::const_iterator it_kvp = kvpResult.constBegin();
it_kvp != kvp_end; ++it_kvp) {
accList[it_kvp.key()].setOnlineBankingSettings(it_kvp.value());
}
return accList;
}
void MyMoneyStorageSql::readAccounts()
{
m_storage->loadAccounts(fetchAccounts());
m_storage->loadAccountId(m_hiIdAccounts);
}
const QMap<QString, MyMoneyMoney> MyMoneyStorageSql::fetchBalance(const QStringList& idList, const QDate& date) const
{
QMap<QString, MyMoneyMoney> returnValue;
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(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);
q.prepare(queryString);
int i = 0;
foreach (const QString& bindVal, idList) {
q.bindValue(QString(":id%1").arg(i), bindVal);
++i;
}
if (!q.exec()) // krazy:exclude=crashy
throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("fetching balance")));
QString id;
QString oldId;
MyMoneyMoney temp;
while (q.next()) {
id = q.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::ActionSplitShares == q.value(0).toString())
temp *= MyMoneyMoney(q.value(1).toString());
else
temp += MyMoneyMoney(q.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 QString& tidList, const QString& dateClause)
{
try {
m_storage->loadTransactions(fetchTransactions(tidList, dateClause));
m_storage->loadTransactionId(getNextTransactionId());
} catch (const MyMoneyException &) {
throw;
}
}
void MyMoneyStorageSql::readTransactions(const MyMoneyTransactionFilter& filter)
{
try {
m_storage->loadTransactions(fetchTransactions(filter));
m_storage->loadTransactionId(getNextTransactionId());
} catch (const MyMoneyException &) {
throw;
}
}
const QMap<QString, MyMoneyTransaction> MyMoneyStorageSql::fetchTransactions(const QString& tidList, const QString& dateClause, bool /*forUpdate*/) const
{
// if (m_transactionListRead) return; // all list already in memory
if (m_displayStatus) {
int transactionsNb = (tidList.isEmpty() ? m_transactions : tidList.size());
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 = m_db.m_tables["kmmTransactions"];
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(this));
q.prepare(t.selectAllString(false) + whereClause + " ORDER BY id;");
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("reading Transaction"))); // krazy:exclude=crashy
const MyMoneyDbTable& ts = 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 <MyMoneyStorageSql*>(this));
QString splitQuery = ts.selectAllString(false) + whereClause
+ " ORDER BY transactionId, splitId;";
qs.prepare(splitQuery);
if (!qs.exec()) throw MYMONEYEXCEPTION(buildError(qs, Q_FUNC_INFO, "reading Splits")); // krazy:exclude=crashy
QString splitTxId = "ZZZ";
MyMoneySplit s;
if (qs.next()) {
splitTxId = qs.value(0).toString();
readSplit(s, qs);
} else {
splitTxId = "ZZZ";
}
QMap <QString, MyMoneyTransaction> 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 (q.next()) {
MyMoneyTransaction tx;
QString txId = GETSTRING(idCol);
tx.setPostDate(GETDATE(postDateCol));
tx.setMemo(GETSTRING(memoCol));
tx.setEntryDate(GETDATE(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();
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();
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 <QString, MyMoneyKeyValueContainer> kvpMap = readKeyValuePairs("TRANSACTION", txList);
QMap<QString, MyMoneyTransaction>::Iterator txMapEnd = txMap.end();
for (QMap<QString, MyMoneyTransaction>::Iterator i = txMap.begin();
i != txMapEnd; ++i) {
i.value().setPairs(kvpMap[i.value().id()].pairs());
if (m_displayStatus) signalProgress(++progress, 0);
}
if ((tidList.isEmpty()) && (dateClause.isEmpty())) {
//qDebug("setting full list read");
}
return txMap;
}
-int MyMoneyStorageSql::splitState(const MyMoneyTransactionFilter::stateOptionE& state) const
+int MyMoneyStorageSql::splitState(const eMyMoney::TransactionFilter::State& state) const
{
int rc = MyMoneySplit::NotReconciled;
switch (state) {
default:
- case MyMoneyTransactionFilter::notReconciled:
+ case eMyMoney::TransactionFilter::State::NotReconciled:
break;
- case MyMoneyTransactionFilter::cleared:
+ case eMyMoney::TransactionFilter::State::Cleared:
rc = MyMoneySplit::Cleared;
break;
- case MyMoneyTransactionFilter::reconciled:
+ case eMyMoney::TransactionFilter::State::Reconciled:
rc = MyMoneySplit::Reconciled;
break;
- case MyMoneyTransactionFilter::frozen:
+ case eMyMoney::TransactionFilter::State::Frozen:
rc = MyMoneySplit::Frozen;
break;
}
return rc;
}
const QMap<QString, MyMoneyTransaction> MyMoneyStorageSql::fetchTransactions(const MyMoneyTransactionFilter& filter) const
{
// 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)) {
alert("Amount Filter Set");
canImplementFilter = false;
}
QString n1, n2;
if (filter.numberFilter(n1, n2)) {
alert("Number filter set");
canImplementFilter = false;
}
int t1;
if (filter.firstType(t1)) {
alert("Type filter set");
canImplementFilter = false;
}
// int s1;
// if (filter.firstState(s1)) {
// alert("State filter set");
// canImplementFilter = false;
// }
QRegExp t2;
if (filter.textFilter(t2)) {
alert("text filter set");
canImplementFilter = false;
}
MyMoneyTransactionFilter::FilterSet s = filter.filterSet();
if (s.singleFilter.validityFilter) {
alert("Validity filter set");
canImplementFilter = false;
}
if (!canImplementFilter) {
QMap<QString, MyMoneyTransaction> transactionList = fetchTransactions();
QMap<QString, MyMoneyTransaction>::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 == 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 <int> 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(splitState(MyMoneyTransactionFilter::stateOptionE(it))));
+ .arg(splitState(eMyMoney::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
}
unsigned long MyMoneyStorageSql::transactionCount(const QString& aid) const
{
if (aid.isEmpty())
return m_transactions;
else
return m_transactionCountMap[aid];
}
void MyMoneyStorageSql::readSplit(MyMoneySplit& s, const QSqlQuery& q) const
{
// 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<QString> tagIdList;
QSqlQuery q1(*const_cast <MyMoneyStorageSql*>(this));
q1.prepare("SELECT tagId from kmmTagSplits where splitId = :id and transactionId = :transactionId");
q1.bindValue(":id", GETSTRING(splitIdCol));
q1.bindValue(":transactionId", GETSTRING(transactionIdCol));
if (!q1.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("reading tagId in Split"))); // krazy:exclude=crashy
while (q1.next())
tagIdList << q1.value(0).toString();
s.setTagIdList(tagIdList);
s.setPayeeId(GETSTRING(payeeIdCol));
s.setReconcileDate(GETDATE(reconcileDateCol));
s.setAction(GETSTRING(actionCol));
s.setReconcileFlag(static_cast<MyMoneySplit::reconcileFlagE>(GETINT(reconcileFlagCol)));
s.setValue(MyMoneyMoney(QStringEmpty(GETSTRING(valueCol))));
s.setShares(MyMoneyMoney(QStringEmpty(GETSTRING(sharesCol))));
s.setPrice(MyMoneyMoney(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;
}
bool MyMoneyStorageSql::isReferencedByTransaction(const QString& id) const
{
//FIXME-ALEX should I add sub query for kmmTagSplits here?
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(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
buildError(q, Q_FUNC_INFO, "error retrieving reference count");
qFatal("Error retrieving reference count"); // definitely shouldn't happen
}
return (0 != q.value(0).toULongLong());
}
void MyMoneyStorageSql::readSchedules()
{
try {
m_storage->loadSchedules(fetchSchedules());
m_storage->loadScheduleId(getNextScheduleId());
} catch (const MyMoneyException &) {
throw;
}
}
const QMap<QString, MyMoneySchedule> MyMoneyStorageSql::fetchSchedules(const QStringList& idList, bool forUpdate) const
{
int schedulesNb = (idList.isEmpty() ? m_schedules : idList.size());
signalProgress(0, schedulesNb, QObject::tr("Loading schedules..."));
int progress = 0;
const MyMoneyDbTable& t = m_db.m_tables["kmmSchedules"];
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(this));
QMap<QString, MyMoneySchedule> sList;
//unsigned long lastId = 0;
const MyMoneyDbTable& ts = m_db.m_tables["kmmSplits"];
QSqlQuery qs(*const_cast <MyMoneyStorageSql*>(this));
qs.prepare(ts.selectAllString(false) + " WHERE transactionId = :id ORDER BY splitId;");
QSqlQuery sq(*const_cast <MyMoneyStorageSql*>(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 += m_driver->forUpdateString();
q.prepare(queryString);
if (! idList.empty()) {
QStringList::ConstIterator bindVal = idList.constBegin();
for (int i = 0; bindVal != idList.constEnd(); ++i, ++bindVal) {
q.bindValue(QString(":id%1").arg(i), *bindVal);
}
}
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, 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 occurenceCol = t.fieldNumber("occurence"); // krazy:exclude=spelling
int occurenceMultiplierCol = t.fieldNumber("occurenceMultiplier");
int paymentTypeCol = t.fieldNumber("paymentType");
int startDateCol = t.fieldNumber("startDate");
int endDateCol = t.fieldNumber("endDate");
int fixedCol = t.fieldNumber("fixed");
int autoEnterCol = t.fieldNumber("autoEnter");
int lastPaymentCol = t.fieldNumber("lastPayment");
int weekendOptionCol = t.fieldNumber("weekendOption");
int nextPaymentDueCol = t.fieldNumber("nextPaymentDue");
while (q.next()) {
MyMoneySchedule s;
QString boolChar;
QString sId = GETSTRING(idCol);
s.setName(GETSTRING(nameCol));
- s.setType(static_cast<MyMoneySchedule::typeE>(GETINT(typeCol)));
- s.setOccurrencePeriod(static_cast<MyMoneySchedule::occurrenceE>(GETINT(occurenceCol)));
+ s.setType(static_cast<eMyMoney::Schedule::Type>(GETINT(typeCol)));
+ s.setOccurrencePeriod(static_cast<eMyMoney::Schedule::Occurrence>(GETINT(occurenceCol)));
s.setOccurrenceMultiplier(GETINT(occurenceMultiplierCol));
- s.setPaymentType(static_cast<MyMoneySchedule::paymentTypeE>(GETINT(paymentTypeCol)));
+ s.setPaymentType(static_cast<eMyMoney::Schedule::PaymentType>(GETINT(paymentTypeCol)));
s.setStartDate(GETDATE(startDateCol));
s.setEndDate(GETDATE(endDateCol));
boolChar = GETSTRING(fixedCol); s.setFixed(boolChar == "Y");
boolChar = GETSTRING(autoEnterCol); s.setAutoEnter(boolChar == "Y");
s.setLastPayment(GETDATE(lastPaymentCol));
- s.setWeekendOption(static_cast<MyMoneySchedule::weekendOptionE>(GETINT(weekendOptionCol)));
+ s.setWeekendOption(static_cast<eMyMoney::Schedule::WeekendOption>(GETINT(weekendOptionCol)));
QDate nextPaymentDue = GETDATE(nextPaymentDueCol);
// convert simple occurrence to compound occurrence
int mult = s.occurrenceMultiplier();
- MyMoneySchedule::occurrenceE occ = s.occurrencePeriod();
+ eMyMoney::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 = m_db.m_tables["kmmTransactions"];
QSqlQuery q2(*const_cast <MyMoneyStorageSql*>(this));
q2.prepare(t.selectAllString(false) + " WHERE id = :id;");
q2.bindValue(":id", s.id());
if (!q2.exec()) throw MYMONEYEXCEPTION(buildError(q2, Q_FUNC_INFO, QString("reading Scheduled Transaction"))); // krazy:exclude=crashy
QSqlRecord rec = q2.record();
if (!q2.next()) throw MYMONEYEXCEPTION(buildError(q2, Q_FUNC_INFO, QString("retrieving scheduled transaction")));
MyMoneyTransaction tx(s.id(), MyMoneyTransaction());
tx.setPostDate(getDate(q2.value(t.fieldNumber("postDate")).toString()));
tx.setMemo(q2.value(t.fieldNumber("memo")).toString());
tx.setEntryDate(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(buildError(qs, Q_FUNC_INFO, "reading Scheduled Splits")); // krazy:exclude=crashy
while (qs.next()) {
MyMoneySplit sp;
readSplit(sp, qs);
tx.addSplit(sp);
}
// if (!m_payeeList.isEmpty())
// readPayees(m_payeeList);
// Process any key value pair
tx.setPairs(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(buildError(q, 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());
//unsigned long id = extractId(s.id().data());
//if(id > lastId)
// lastId = id;
signalProgress(++progress, 0);
}
return sList;
}
void MyMoneyStorageSql::readSecurities()
{
try {
m_storage->loadSecurities(fetchSecurities());
m_storage->loadSecurityId(getNextSecurityId());
} catch (const MyMoneyException &) {
throw;
}
}
const QMap<QString, MyMoneySecurity> MyMoneyStorageSql::fetchSecurities(const QStringList& /*idList*/, bool /*forUpdate*/) const
{
signalProgress(0, m_securities, QObject::tr("Loading securities..."));
int progress = 0;
QMap<QString, MyMoneySecurity> sList;
unsigned long lastId = 0;
const MyMoneyDbTable& t = m_db.m_tables["kmmSecurities"];
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(this));
q.prepare(t.selectAllString(false) + " ORDER BY id;");
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, 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 (q.next()) {
MyMoneySecurity e;
QString eid;
eid = GETSTRING(idCol);
e.setName(GETSTRING(nameCol));
e.setTradingSymbol(GETSTRING(symbolCol));
e.setSecurityType(static_cast<Security>(GETINT(typeCol)));
e.setRoundingMethod(static_cast<AlkValue::RoundingMethod>(GETINT(roundingMethodCol)));
int saf = GETINT(smallestAccountFractionCol);
int pp = GETINT(pricePrecisionCol);
e.setTradingCurrency(GETSTRING(tradingCurrencyCol));
e.setTradingMarket(GETSTRING(tradingMarketCol));
if (e.tradingCurrency().isEmpty())
e.setTradingCurrency(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(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;
unsigned long id = extractId(security.id());
if (id > lastId)
lastId = id;
signalProgress(++progress, 0);
}
return sList;
}
void MyMoneyStorageSql::readPrices()
{
// try {
// m_storage->addPrice(MyMoneyPrice(from, to, date, rate, source));
// } catch (const MyMoneyException &) {
// throw;
// }
}
MyMoneyPrice MyMoneyStorageSql::fetchSinglePrice(const QString& fromId, const QString& toId, const QDate& date_, bool exactDate, bool /*forUpdate*/) const
{
const MyMoneyDbTable& t = 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 q(*const_cast <MyMoneyStorageSql*>(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;";
q.prepare(queryString);
QDate date(date_);
if (!date.isValid())
date = QDate::currentDate();
q.bindValue(":fromId", fromId);
q.bindValue(":toId", toId);
q.bindValue(":priceDate", date.addDays(1).toString(Qt::ISODate));
if (exactDate)
q.bindValue(":exactDate", date.toString(Qt::ISODate));
if (! q.exec()) return MyMoneyPrice(); // krazy:exclude=crashy
if (q.next()) {
return MyMoneyPrice(fromId,
toId,
GETDATE(priceDateCol),
MyMoneyMoney(GETSTRING(priceCol)),
GETSTRING(priceSourceCol));
}
return MyMoneyPrice();
}
const MyMoneyPriceList MyMoneyStorageSql::fetchPrices(const QStringList& fromIdList, const QStringList& toIdList, bool forUpdate) const
{
int pricesNb = (fromIdList.isEmpty() ? m_prices : fromIdList.size());
signalProgress(0, pricesNb, QObject::tr("Loading prices..."));
int progress = 0;
const_cast <MyMoneyStorageSql*>(this)->m_readingPrices = true;
MyMoneyPriceList pList;
const MyMoneyDbTable& t = m_db.m_tables["kmmPrices"];
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(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 += m_driver->forUpdateString();
queryString += ';';
q.prepare(queryString);
if (! fromIdList.empty()) {
QStringList::ConstIterator bindVal = fromIdList.constBegin();
for (int i = 0; bindVal != fromIdList.constEnd(); ++i, ++bindVal) {
q.bindValue(QString(":fromId%1").arg(i), *bindVal);
}
}
if (! toIdList.empty()) {
QStringList::ConstIterator bindVal = toIdList.constBegin();
for (int i = 0; bindVal != toIdList.constEnd(); ++i, ++bindVal) {
q.bindValue(QString(":toId%1").arg(i), *bindVal);
}
}
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, 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 (q.next()) {
QString from = GETSTRING(fromIdCol);
QString to = GETSTRING(toIdCol);
QDate date = GETDATE(priceDateCol);
pList [MyMoneySecurityPair(from, to)].insert(date, MyMoneyPrice(from, to, date, MyMoneyMoney(GETSTRING(priceCol)), GETSTRING(priceSourceCol)));
signalProgress(++progress, 0);
}
const_cast <MyMoneyStorageSql*>(this)->m_readingPrices = false;
return pList;
}
void MyMoneyStorageSql::readCurrencies()
{
try {
m_storage->loadCurrencies(fetchCurrencies());
} catch (const MyMoneyException &) {
throw;
}
}
const QMap<QString, MyMoneySecurity> MyMoneyStorageSql::fetchCurrencies(const QStringList& idList, bool forUpdate) const
{
int currenciesNb = (idList.isEmpty() ? m_currencies : idList.size());
signalProgress(0, currenciesNb, QObject::tr("Loading currencies..."));
int progress = 0;
QMap<QString, MyMoneySecurity> cList;
const MyMoneyDbTable& t = m_db.m_tables["kmmCurrencies"];
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(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 += m_driver->forUpdateString();
queryString += ';';
q.prepare(queryString);
if (! idList.empty()) {
QStringList::ConstIterator bindVal = idList.constBegin();
for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) {
q.bindValue(QString(":id%1").arg(i), *bindVal);
}
}
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, 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 (q.next()) {
QString id;
MyMoneySecurity c;
QChar symbol[3];
id = GETSTRING(ISOcodeCol);
c.setName(GETSTRING(nameCol));
c.setSecurityType(static_cast<Security>(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);
signalProgress(++progress, 0);
}
return cList;
}
void MyMoneyStorageSql::readReports()
{
try {
m_storage->loadReports(fetchReports());
m_storage->loadReportId(getNextReportId());
} catch (const MyMoneyException &) {
throw;
}
}
const QMap<QString, MyMoneyReport> MyMoneyStorageSql::fetchReports(const QStringList& /*idList*/, bool /*forUpdate*/) const
{
signalProgress(0, m_reports, QObject::tr("Loading reports..."));
int progress = 0;
const MyMoneyDbTable& t = m_db.m_tables["kmmReportConfig"];
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(this));
q.prepare(t.selectAllString(true));
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("reading reports"))); // krazy:exclude=crashy
int xmlCol = t.fieldNumber("XML");
QMap<QString, MyMoneyReport> rList;
while (q.next()) {
QDomDocument d;
d.setContent(GETSTRING(xmlCol), false);
QDomNode child = d.firstChild();
child = child.firstChild();
MyMoneyReport report;
if (report.read(child.toElement()))
rList[report.id()] = report;
signalProgress(++progress, 0);
}
return rList;
}
const QMap<QString, MyMoneyBudget> MyMoneyStorageSql::fetchBudgets(const QStringList& idList, bool forUpdate) const
{
int budgetsNb = (idList.isEmpty() ? m_budgets : idList.size());
signalProgress(0, budgetsNb, QObject::tr("Loading budgets..."));
int progress = 0;
const MyMoneyDbTable& t = m_db.m_tables["kmmBudgetConfig"];
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(this));
QString queryString(t.selectAllString(false));
if (! idList.empty()) {
queryString += " WHERE id = '" + idList.join("' OR id = '") + '\'';
}
if (forUpdate)
queryString += m_driver->forUpdateString();
queryString += ';';
q.prepare(queryString);
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("reading budgets"))); // krazy:exclude=crashy
QMap<QString, MyMoneyBudget> budgets;
int xmlCol = t.fieldNumber("XML");
while (q.next()) {
QDomDocument d;
d.setContent(GETSTRING(xmlCol), false);
QDomNode child = d.firstChild();
child = child.firstChild();
MyMoneyBudget budget(child.toElement());
budgets.insert(budget.id(), budget);
signalProgress(++progress, 0);
}
return budgets;
}
void MyMoneyStorageSql::readBudgets()
{
m_storage->loadBudgets(fetchBudgets());
}
const MyMoneyKeyValueContainer MyMoneyStorageSql::readKeyValuePairs(const QString& kvpType, const QString& kvpId) const
{
MyMoneyKeyValueContainer list;
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(this));
q.prepare("SELECT kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type and kvpId = :id;");
q.bindValue(":type", kvpType);
q.bindValue(":id", kvpId);
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("reading Kvp for %1 %2").arg(kvpType) // krazy:exclude=crashy
.arg(kvpId)));
while (q.next()) list.setValue(q.value(0).toString(), q.value(1).toString());
return (list);
}
const QHash<QString, MyMoneyKeyValueContainer> MyMoneyStorageSql::readKeyValuePairs(const QString& kvpType, const QStringList& kvpIdList) const
{
QHash<QString, MyMoneyKeyValueContainer> retval;
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(this));
QString idList;
if (!kvpIdList.empty()) {
idList = QString(" and kvpId IN ('%1')").arg(kvpIdList.join("', '"));
}
QString query = QString("SELECT kvpId, kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type %1 order by kvpId;").arg(idList);
q.prepare(query);
q.bindValue(":type", kvpType);
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, 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 (q.next()) {
retval[q.value(0).toString()].setValue(q.value(1).toString(), q.value(2).toString());
}
return (retval);
}
template<long unsigned MyMoneyStorageSql::* cache>
long unsigned int MyMoneyStorageSql::getNextId(const QString& table, const QString& id, const int prefixLength) const
{
Q_CHECK_PTR(cache);
if (this->*cache == 0) {
MyMoneyStorageSql* nonConstThis = const_cast<MyMoneyStorageSql*>(this);
nonConstThis->*cache = 1 + nonConstThis->highestNumberFromIdString(table, id, prefixLength);
}
Q_ASSERT(this->*cache > 0); // everything else is never a valid id
return this->*cache;
}
long unsigned MyMoneyStorageSql::getNextBudgetId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdBudgets>(QLatin1String("kmmBudgetConfig"), QLatin1String("id"), 1);
}
long unsigned MyMoneyStorageSql::getNextAccountId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdAccounts>(QLatin1String("kmmAccounts"), QLatin1String("id"), 1);
}
long unsigned MyMoneyStorageSql::getNextInstitutionId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdInstitutions>(QLatin1String("kmmInstitutions"), QLatin1String("id"), 1);
}
long unsigned MyMoneyStorageSql::getNextPayeeId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdPayees>(QLatin1String("kmmPayees"), QLatin1String("id"), 1);
}
long unsigned MyMoneyStorageSql::getNextTagId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdTags>(QLatin1String("kmmTags"), QLatin1String("id"), 1);
}
long unsigned MyMoneyStorageSql::getNextReportId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdReports>(QLatin1String("kmmReportConfig"), QLatin1String("id"), 1);
}
long unsigned MyMoneyStorageSql::getNextScheduleId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdSchedules>(QLatin1String("kmmSchedules"), QLatin1String("id"), 3);
}
long unsigned MyMoneyStorageSql::getNextSecurityId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdSecurities>(QLatin1String("kmmSecurities"), QLatin1String("id"), 1);
}
long unsigned MyMoneyStorageSql::getNextTransactionId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdTransactions>(QLatin1String("kmmTransactions"), QLatin1String("id"), 1);
}
long unsigned MyMoneyStorageSql::getNextOnlineJobId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdOnlineJobs>(QLatin1String("kmmOnlineJobs"), QLatin1String("id"), 1);
}
long unsigned MyMoneyStorageSql::getNextPayeeIdentifierId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdPayeeIdentifier>(QLatin1String("kmmPayeeIdentifier"), QLatin1String("id"), 5);
}
long unsigned int MyMoneyStorageSql::getNextCostCenterId() const
{
return getNextId<&MyMoneyStorageSql::m_hiIdCostCenter>(QLatin1String("kmmCostCenterIdentifier"), QLatin1String("id"), 5);
}
long unsigned MyMoneyStorageSql::incrementBudgetId()
{
m_hiIdBudgets = getNextBudgetId() + 1;
return (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.
*/
long unsigned MyMoneyStorageSql::incrementAccountId()
{
m_hiIdAccounts = getNextAccountId() + 1;
return (m_hiIdAccounts - 1);
}
long unsigned MyMoneyStorageSql::incrementInstitutionId()
{
m_hiIdInstitutions = getNextInstitutionId() + 1;
return (m_hiIdInstitutions - 1);
}
long unsigned MyMoneyStorageSql::incrementPayeeId()
{
m_hiIdPayees = getNextPayeeId() + 1;
return (m_hiIdPayees - 1);
}
long unsigned MyMoneyStorageSql::incrementTagId()
{
m_hiIdTags = getNextTagId() + 1;
return (m_hiIdTags - 1);
}
long unsigned MyMoneyStorageSql::incrementReportId()
{
m_hiIdReports = getNextReportId() + 1;
return (m_hiIdReports - 1);
}
long unsigned MyMoneyStorageSql::incrementScheduleId()
{
m_hiIdSchedules = getNextScheduleId() + 1;
return (m_hiIdSchedules - 1);
}
long unsigned MyMoneyStorageSql::incrementSecurityId()
{
m_hiIdSecurities = getNextSecurityId() + 1;
return (m_hiIdSecurities - 1);
}
long unsigned MyMoneyStorageSql::incrementTransactionId()
{
m_hiIdTransactions = getNextTransactionId() + 1;
return (m_hiIdTransactions - 1);
}
long unsigned int MyMoneyStorageSql::incrementOnlineJobId()
{
m_hiIdOnlineJobs = getNextOnlineJobId() + 1;
return (m_hiIdOnlineJobs - 1);
}
long unsigned int MyMoneyStorageSql::incrementPayeeIdentfierId()
{
m_hiIdPayeeIdentifier = getNextPayeeIdentifierId() + 1;
return (m_hiIdPayeeIdentifier - 1);
}
long unsigned int MyMoneyStorageSql::incrementCostCenterId()
{
m_hiIdCostCenter = getNextCostCenterId() + 1;
return (m_hiIdCostCenter - 1);
}
void MyMoneyStorageSql::loadAccountId(const unsigned long& id)
{
m_hiIdAccounts = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadTransactionId(const unsigned long& id)
{
m_hiIdTransactions = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadPayeeId(const unsigned long& id)
{
m_hiIdPayees = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadTagId(const unsigned long& id)
{
m_hiIdTags = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadInstitutionId(const unsigned long& id)
{
m_hiIdInstitutions = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadScheduleId(const unsigned long& id)
{
m_hiIdSchedules = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadSecurityId(const unsigned long& id)
{
m_hiIdSecurities = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadReportId(const unsigned long& id)
{
m_hiIdReports = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadBudgetId(const unsigned long& id)
{
m_hiIdBudgets = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadOnlineJobId(const long unsigned int& id)
{
m_hiIdOnlineJobs = id;
writeFileInfo();
}
void MyMoneyStorageSql::loadPayeeIdentifierId(const long unsigned int& id)
{
m_hiIdPayeeIdentifier = id;
writeFileInfo();
}
//****************************************************
long unsigned MyMoneyStorageSql::calcHighId(const long unsigned& i, const QString& id)
{
QString nid = id;
long unsigned high = (unsigned long) nid.remove(QRegExp("[A-Z]*")).toULongLong();
return std::max(high, i);
}
void MyMoneyStorageSql::setProgressCallback(void(*callback)(int, int, const QString&))
{
m_progressCallback = callback;
}
void MyMoneyStorageSql::signalProgress(int current, int total, const QString& msg) const
{
if (m_progressCallback != 0)
(*m_progressCallback)(current, total, msg);
}
// **************************** Error display routine *******************************
QString& MyMoneyStorageSql::buildError(const QSqlQuery& q, const QString& function,
const QString& messageb) const
{
return (buildError(q, function, messageb, this));
}
QString& MyMoneyStorageSql::buildError(const QSqlQuery& q, const QString& function,
const QString& message, const QSqlDatabase* db) const
{
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 = q.lastError();
s += QString("\nExecuted: %1").arg(q.executedQuery());
s += QString("\nQuery error No %1: %2").arg(e.number()).arg(e.text());
s += QString("\nError type %1").arg(e.type());
const_cast <MyMoneyStorageSql*>(this)->m_error = s;
qDebug("%s", qPrintable(s));
const_cast <MyMoneyStorageSql*>(this)->cancelCommitUnit(function);
return (const_cast <MyMoneyStorageSql*>(this)->m_error);
}
QDate MyMoneyStorageSql::m_startDate = QDate(1900, 1, 1);
void MyMoneyStorageSql::setStartDate(const QDate& startDate)
{
m_startDate = startDate;
}
const QMap< QString, MyMoneyCostCenter > MyMoneyStorageSql::fetchCostCenters(const QStringList& idList, bool forUpdate) const
{
Q_UNUSED(forUpdate);
MyMoneyDbTransaction trans(const_cast <MyMoneyStorageSql&>(*this), Q_FUNC_INFO);
if (m_displayStatus) {
int costCenterNb = (idList.isEmpty() ? 100 : idList.size());
signalProgress(0, costCenterNb, QObject::tr("Loading cost center..."));
}
int progress = 0;
QMap<QString, MyMoneyCostCenter> costCenterList;
//unsigned long lastId;
const MyMoneyDbTable& t = m_db.m_tables["kmmCostCenter"];
QSqlQuery q(*const_cast <MyMoneyStorageSql*>(this));
if (idList.isEmpty()) {
q.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 += ')';
q.prepare(t.selectAllString(false) + whereClause);
}
if (!q.exec()) throw MYMONEYEXCEPTION(buildError(q, Q_FUNC_INFO, QString("reading CostCenter"))); // krazy:exclude=crashy
const int idCol = t.fieldNumber("id");
const int nameCol = t.fieldNumber("name");
while (q.next()) {
MyMoneyCostCenter costCenter;
QString pid = GETSTRING(idCol);
costCenter.setName(GETSTRING(nameCol));
costCenterList[pid] = MyMoneyCostCenter(pid, costCenter);
if (m_displayStatus) signalProgress(++progress, 0);
}
return costCenterList;
}
diff --git a/kmymoney/mymoney/storage/mymoneystoragesql.h b/kmymoney/mymoney/storage/mymoneystoragesql.h
index 725811b72..5cd14e705 100644
--- a/kmymoney/mymoney/storage/mymoneystoragesql.h
+++ b/kmymoney/mymoney/storage/mymoneystoragesql.h
@@ -1,609 +1,609 @@
/*
* This file is part of KMyMoney, A Personal Finance Manager by KDE
* Copyright (C) 2005 Tony Bloomfield <tonybloom@users.sourceforge.net>
* Copyright (C) Fernando Vilas <fvilas@iname.com>
* Copyright (C) 2014 Christian Dávid <christian-david@web.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef MYMONEYSTORAGESQL_H
#define MYMONEYSTORAGESQL_H
#include <limits>
#include <QPair>
#include <QString>
#include <QDebug>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QList>
#include <QStack>
#include <QUrl>
#include <QExplicitlySharedDataPointer>
class QIODevice;
#include "imymoneystorageformat.h"
#include "mymoneyinstitution.h"
#include "mymoneypayee.h"
#include "mymoneytag.h"
#include "mymoneyaccount.h"
#include "mymoneysecurity.h"
#include "mymoneyprice.h"
#include "mymoneyreport.h"
#include "mymoneybudget.h"
#include "mymoneykeyvaluecontainer.h"
#include "mymoneytransaction.h"
#include "mymoneytransactionfilter.h"
#include "mymoneydbdef.h"
#include "mymoneydbdriver.h"
#include "onlinejob.h"
#include "payeeidentifier/payeeidentifier.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.
template <class T1, class T2> struct QPair;
class IMyMoneyStorage;
class MyMoneyCostCenter;
class MyMoneyMoney;
class MyMoneySchedule;
class MyMoneySplit;
class MyMoneyTransaction;
class databaseStoreableObject;
class FilterFail
{
public:
FilterFail(const MyMoneyTransactionFilter& filter) : m_filter(filter) {}
inline bool operator()(const QPair<QString, MyMoneyTransaction>& transactionPair) {
return (*this)(transactionPair.second);
}
inline bool operator()(const MyMoneyTransaction& transaction) {
return (! m_filter.match(transaction)) && (m_filter.matchingSplits().count() == 0);
}
private:
MyMoneyTransactionFilter m_filter;
};
class MyMoneyStorageSql;
//*****************************************************************************
// 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:
MyMoneyDbTransaction(MyMoneyStorageSql& db, const QString& name);
~MyMoneyDbTransaction();
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:
MyMoneySqlQuery(MyMoneyStorageSql* db = 0);
virtual ~MyMoneySqlQuery();
bool exec();
bool exec(const QString & query);
bool prepare(const QString & query);
};
class IMyMoneySerialize;
/**
* 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 MyMoneyStorageSql : public IMyMoneyStorageFormat, public QSqlDatabase, public QSharedData
{
friend class MyMoneyDbDef;
KMM_MYMONEY_UNIT_TESTABLE
public:
explicit MyMoneyStorageSql(IMyMoneySerialize *storage, const QUrl& = QUrl());
virtual ~MyMoneyStorageSql();
unsigned int currentVersion() const {
return (m_db.currentVersion());
};
/**
* 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
*
*/
const QString& lastError() const {
return (m_error);
};
/**
* MyMoneyStorageSql get highest ID number from the database
*
* @return : highest ID number
*/
long unsigned highestNumberFromIdString(QString tableName, QString tableField, int prefixLength);
/**
* 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);
unsigned long transactionCount(const QString& aid = QString()) const;
inline const QHash<QString, unsigned long> transactionCountMap() const {
return (m_transactionCountMap);
};
/**
* 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<MyMoneyAccount>& acc);
/**
* the storage manager also needs the following read entry points
*/
const QMap<QString, MyMoneyAccount> fetchAccounts(const QStringList& idList = QStringList(), bool forUpdate = false) const;
const QMap<QString, MyMoneyMoney> fetchBalance(const QStringList& id, const QDate& date) const;
const QMap<QString, MyMoneyBudget> fetchBudgets(const QStringList& idList = QStringList(), bool forUpdate = false) const;
const QMap<QString, MyMoneySecurity> fetchCurrencies(const QStringList& idList = QStringList(), bool forUpdate = false) const;
const QMap<QString, MyMoneyInstitution> fetchInstitutions(const QStringList& idList = QStringList(), bool forUpdate = false) const;
const QMap<QString, MyMoneyPayee> fetchPayees(const QStringList& idList = QStringList(), bool forUpdate = false) const;
const QMap<QString, MyMoneyTag> fetchTags(const QStringList& idList = QStringList(), bool forUpdate = false) const;
const QMap<QString, onlineJob> fetchOnlineJobs(const QStringList& idList = QStringList(), bool forUpdate = false) const;
const QMap<QString, MyMoneyCostCenter> fetchCostCenters(const QStringList& idList = QStringList(), bool forUpdate = false) const;
const MyMoneyPriceList fetchPrices(const QStringList& fromIdList = QStringList(), const QStringList& toIdList = QStringList(), bool forUpdate = false) const;
MyMoneyPrice fetchSinglePrice(const QString& fromId, const QString& toId, const QDate& date_, bool exactDate, bool = false) const;
const QMap<QString, MyMoneyReport> fetchReports(const QStringList& idList = QStringList(), bool forUpdate = false) const;
const QMap<QString, MyMoneySchedule> fetchSchedules(const QStringList& idList = QStringList(), bool forUpdate = false) const;
const QMap<QString, MyMoneySecurity> fetchSecurities(const QStringList& idList = QStringList(), bool forUpdate = false) const;
const QMap<QString, MyMoneyTransaction> fetchTransactions(const QString& tidList = QString(), const QString& dateClause = QString(), bool forUpdate = false) const;
const QMap<QString, MyMoneyTransaction> fetchTransactions(const MyMoneyTransactionFilter& filter) const;
payeeIdentifier fetchPayeeIdentifier(const QString& id) const;
const QMap<QString, payeeIdentifier> fetchPayeeIdentifiers(const QStringList& idList = QStringList()) const;
bool isReferencedByTransaction(const QString& id) const;
void readPayees(const QString&);
void readPayees(const QList<QString>& payeeList = QList<QString>());
void readTags(const QString&);
void readTags(const QList<QString>& tagList = QList<QString>());
void readTransactions(const MyMoneyTransactionFilter& filter);
void setProgressCallback(void(*callback)(int, int, const QString&));
virtual void readFile(QIODevice* s, IMyMoneySerialize* storage) {
Q_UNUSED(s); Q_UNUSED(storage)
};
virtual void writeFile(QIODevice* s, IMyMoneySerialize* storage) {
Q_UNUSED(s); Q_UNUSED(storage)
};
void startCommitUnit(const QString& callingFunction);
bool endCommitUnit(const QString& callingFunction);
void cancelCommitUnit(const QString& callingFunction);
long unsigned getRecCount(const QString& table) const;
long unsigned getNextBudgetId() const;
long unsigned getNextAccountId() const;
long unsigned getNextInstitutionId() const;
long unsigned getNextPayeeId() const;
long unsigned getNextTagId() const;
long unsigned getNextOnlineJobId() const;
long unsigned getNextPayeeIdentifierId() const;
long unsigned getNextReportId() const;
long unsigned getNextScheduleId() const;
long unsigned getNextSecurityId() const;
long unsigned getNextTransactionId() const;
long unsigned getNextCostCenterId() const;
long unsigned incrementBudgetId();
long unsigned incrementAccountId();
long unsigned incrementInstitutionId();
long unsigned incrementPayeeId();
long unsigned incrementTagId();
long unsigned incrementReportId();
long unsigned incrementScheduleId();
long unsigned incrementSecurityId();
long unsigned incrementTransactionId();
long unsigned incrementOnlineJobId();
long unsigned incrementPayeeIdentfierId();
long unsigned incrementCostCenterId();
void loadAccountId(const unsigned long& id);
void loadTransactionId(const unsigned long& id);
void loadPayeeId(const unsigned long& id);
void loadTagId(const unsigned long& id);
void loadInstitutionId(const unsigned long& id);
void loadScheduleId(const unsigned long& id);
void loadSecurityId(const unsigned long& id);
void loadReportId(const unsigned long& id);
void loadBudgetId(const unsigned long& id);
void loadOnlineJobId(const unsigned long& id);
void loadPayeeIdentifierId(const unsigned long& id);
void loadCostCenterId(const unsigned long& 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:
bool fileExists(const QString& dbName);
/** @brief a function to build a comprehensive error message for an SQL error */
QString& buildError(const QSqlQuery& q, const QString& function, const QString& message) const;
/** @copydoc buildError */
QString& buildError(const QSqlQuery& q, const QString& function, const QString& message,
const QSqlDatabase*) const;
/**
* @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();
void writePayees();
void writeTags();
void writeAccounts();
void writeTransactions();
void writeSchedules();
void writeSecurities();
void writePrices();
void writeCurrencies();
void writeFileInfo();
void writeReports();
void writeBudgets();
void writeOnlineJobs();
/** @} */
/**
* @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<MyMoneyInstitution>& iList, QSqlQuery& q);
void writePayee(const MyMoneyPayee& p, QSqlQuery& q, bool isUserInfo = false);
void writeTag(const MyMoneyTag& p, QSqlQuery& q);
void writeAccountList(const QList<MyMoneyAccount>& accList, QSqlQuery& q);
void writeTransaction(const QString& txId, const MyMoneyTransaction& tx, QSqlQuery& q, const QString& type);
void writeSplits(const QString& txId, const QString& type, const QList<MyMoneySplit>& splitList);
void writeTagSplitsList(const QString& txId, const QList<MyMoneySplit>& splitList, const QList<int>& splitIdList);
void writeSplitList(const QString& txId, const QList<MyMoneySplit>& splitList, const QString& type, const QList<int>& splitIdList, QSqlQuery& q);
void writeSchedule(const MyMoneySchedule& sch, QSqlQuery& q, bool insert);
void writeSecurity(const MyMoneySecurity& security, QSqlQuery& q);
void writePricePair(const MyMoneyPriceEntries& p);
void writePrice(const MyMoneyPrice& p);
void writeCurrency(const MyMoneySecurity& currency, QSqlQuery& q);
void writeReport(const MyMoneyReport& rep, QSqlQuery& q);
void writeBudget(const MyMoneyBudget& bud, QSqlQuery& q);
void writeKeyValuePairs(const QString& kvpType, const QVariantList& kvpId, const QList<QMap<QString, QString> >& pairs);
void writeOnlineJob(const onlineJob& job, QSqlQuery& query);
void writePayeeIdentifier(const payeeIdentifier& pid, QSqlQuery& query);
/** @} */
/**
* @name readMethods
* @{
*/
void readFileInfo();
void readLogonData();
void readUserInformation();
void readInstitutions();
void readAccounts();
void readTransactions(const QString& tidList = QString(), const QString& dateClause = QString());
void readSplit(MyMoneySplit& s, const QSqlQuery& q) const;
const MyMoneyKeyValueContainer readKeyValuePairs(const QString& kvpType, const QString& kvpId) const;
const QHash<QString, MyMoneyKeyValueContainer> readKeyValuePairs(const QString& kvpType, const QStringList& kvpIdList) const;
void readSchedules();
void readSecurities();
void readPrices();
void readCurrencies();
void readReports();
void readBudgets();
/** @} */
template<long unsigned MyMoneyStorageSql::* cache>
long unsigned int getNextId(const QString& table, const QString& id, const int prefixLength) const;
void deleteTransaction(const QString& id);
void deleteTagSplitsList(const QString& txId, const QList<int>& splitIdList);
void deleteSchedule(const QString& id);
void deleteKeyValuePairs(const QString& kvpType, const QVariantList& kvpId);
long unsigned calcHighId(const long unsigned&, const QString&);
void setVersion(const QString& version);
void signalProgress(int current, int total, const QString& = "") const;
void (*m_progressCallback)(int, int, const QString&);
//void startCommitUnit (const QString& callingFunction);
//void endCommitUnit (const QString& callingFunction);
//void cancelCommitUnit (const QString& callingFunction);
- int splitState(const MyMoneyTransactionFilter::stateOptionE& state) const;
+ int splitState(const eMyMoney::TransactionFilter::State& state) const;
inline const QDate getDate(const QString& date) const {
return (date.isNull() ? QDate() : QDate::fromString(date, Qt::ISODate));
}
inline const QDateTime getDateTime(const QString& date) const {
return (date.isNull() ? QDateTime() : QDateTime::fromString(date, Qt::ISODate));
}
// open routines
/**
* 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);
int upgradeDb();
int upgradeToV1();
int upgradeToV2();
int upgradeToV3();
int upgradeToV4();
int upgradeToV5();
int upgradeToV6();
int upgradeToV7();
int upgradeToV8();
int upgradeToV9();
int upgradeToV10();
int upgradeToV11();
int createTables();
void createTable(const MyMoneyDbTable& t, int version = std::numeric_limits<int>::max());
bool alterTable(const MyMoneyDbTable& t, int fromVersion);
void clean();
int isEmpty();
// for bug 252841
const QStringList tables(QSql::TableType tt) {
return (m_driver->tables(tt, static_cast<const QSqlDatabase&>(*this)));
};
/**
* @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);
void insertStorableObject(const databaseStoreableObject& obj, const QString& id);
void updateStorableObject(const databaseStoreableObject& obj, const QString& id);
void deleteStorableObject(const databaseStoreableObject& obj, const QString& id);
// data
QExplicitlySharedDataPointer<MyMoneyDbDriver> m_driver;
MyMoneyDbDef m_db;
unsigned int m_dbVersion;
IMyMoneySerialize *m_storage;
IMyMoneyStorage *m_storagePtr;
// input options
bool m_loadAll; // preload all data
bool m_override; // override open if already in use
// error message
QString m_error;
// record counts
long unsigned m_institutions;
long unsigned m_accounts;
long unsigned m_payees;
long unsigned m_tags;
long unsigned m_transactions;
long unsigned m_splits;
long unsigned m_securities;
long unsigned m_prices;
long unsigned m_currencies;
long unsigned m_schedules;
long unsigned m_reports;
long unsigned m_kvps;
long unsigned m_budgets;
long unsigned m_onlineJobs;
long unsigned m_payeeIdentifier;
// Cache for next id to use
// value 0 means data is not available and has to be loaded from the database
long unsigned m_hiIdInstitutions;
long unsigned m_hiIdPayees;
long unsigned m_hiIdTags;
long unsigned m_hiIdAccounts;
long unsigned m_hiIdTransactions;
long unsigned m_hiIdSchedules;
long unsigned m_hiIdSecurities;
long unsigned m_hiIdReports;
long unsigned m_hiIdBudgets;
long unsigned m_hiIdOnlineJobs;
long unsigned m_hiIdPayeeIdentifier;
long unsigned 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;
void alert(QString s) const {
qDebug() << s;
}; // FIXME: remove...
/** 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<QString> 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<QString, unsigned long> 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
//Disable copying
MyMoneyStorageSql(const MyMoneyStorageSql& rhs);
MyMoneyStorageSql& operator= (const MyMoneyStorageSql& rhs);
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<QString> m_loadedStoragePlugins;
};
#endif // MYMONEYSTORAGESQL_H
diff --git a/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp b/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp
index 90af811a3..df466bf2a 100644
--- a/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp
+++ b/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp
@@ -1,2340 +1,2348 @@
/***************************************************************************
mymoneydatabasemgrtest.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 <pwd.h>
#include <unistd.h>
#include <iostream>
#include <QtTest/QtTest>
#include "mymoneytestutils.h"
#include "mymoneyfile.h"
#include "onlinetasks/dummy/tasks/dummytask.h"
+#include "mymoneyenums.h"
+
+using namespace eMyMoney;
+
QTEST_GUILESS_MAIN(MyMoneyDatabaseMgrTest)
MyMoneyDatabaseMgrTest::MyMoneyDatabaseMgrTest()
: 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()
{
testStepTimer.start();
m = new MyMoneyDatabaseMgr;
// Create file and close it to release possible read-write locks
m_file.open();
m_file.close();
}
void MyMoneyDatabaseMgrTest::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()
{
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());
QCOMPARE(m->institutionList().count(), 0);
QList<MyMoneyAccount> 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().count(), 0);
QCOMPARE(m->m_creationDate, QDate::currentDate());
}
void MyMoneyDatabaseMgrTest::setupUrl(const QString& fname)
{
struct passwd * pwd = getpwuid(geteuid());
QString m_userName;
if (pwd != 0) {
m_userName = QString(pwd->pw_name);
}
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)
{
if (src.open(QIODevice::ReadOnly)) {
if (dest.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
dest.write(src.readAll());
dest.close();
}
src.close();
}
}
void MyMoneyDatabaseMgrTest::testBadConnections()
{
// Check a connection that exists but has empty tables
setupUrl(m_file.fileName());
try {
QExplicitlySharedDataPointer <MyMoneyStorageSql> sql = m->connectToDatabase(m_url);
QVERIFY(sql);
QEXPECT_FAIL("", "Will fix when correct behaviour in this case is clear.", Continue);
QVERIFY(sql->open(m_url, QIODevice::ReadWrite) != 0);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::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 <MyMoneyStorageSql> sql = m->connectToDatabase(m_url);
QVERIFY(0 != 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)) {
MyMoneyFile::instance()->attachStorage(m);
QVERIFY(sql->writeFile());
sql->close();
copyDatabaseFile(m_file, m_emptyFile);
m_haveEmptyDataBase = true;
} else {
m_canOpen = false;
}
}
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::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 <MyMoneyStorageSql> sql = m->connectToDatabase(m_url);
QVERIFY(sql);
int openStatus = 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()
{
testAttachDb();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
try {
((QSqlDatabase*)(m->m_sql.data()))->close();
QList<MyMoneyAccount> accList;
m->accountList(accList);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::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);
}
void MyMoneyDatabaseMgrTest::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->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);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::testIsStandardAccount()
{
testAttachDb();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
QVERIFY(m->isStandardAccount(STD_ACC_LIABILITY) == true);
QVERIFY(m->isStandardAccount(STD_ACC_ASSET) == true);
QVERIFY(m->isStandardAccount(STD_ACC_EXPENSE) == true);
QVERIFY(m->isStandardAccount(STD_ACC_INCOME) == true);
QVERIFY(m->isStandardAccount(STD_ACC_EQUITY) == true);
QVERIFY(m->isStandardAccount("A0002") == false);
}
void MyMoneyDatabaseMgrTest::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");
m->addAccount(a);
QCOMPARE(m->accountId(), 1ul);
QList<MyMoneyAccount> 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()
{
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);
// 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()
{
testNewAccount();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
MyMoneyAccount a, b;
b.setName("Account2");
b.setNumber("Acc2");
m->addAccount(b);
m->setDirty();
QVERIFY(m->accountId() == 2);
QList<MyMoneyAccount> accList;
m->accountList(accList);
QVERIFY(accList.count() == 2);
// try to add account to undefined account
try {
MyMoneyAccount c("UnknownID", b);
m->addAccount(c, a);
QFAIL("Exception expected");
} catch (const MyMoneyException &) {
}
QVERIFY(m->dirty() == false);
// now try to add account 1 as sub-account to account 2
try {
a = m->account("A000001");
QVERIFY(m->asset().accountList().count() == 0);
m->addAccount(b, a);
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()
{
testAttachDb();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
MyMoneyInstitution i;
i.setName("Inst Name");
m->addInstitution(i);
QVERIFY(m->institutionList().count() == 1);
QVERIFY(m->institutionId() == 1);
QVERIFY((*(m->institutionList().begin())).name() == "Inst Name");
QVERIFY((*(m->institutionList().begin())).id() == "I000001");
}
void MyMoneyDatabaseMgrTest::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);
// now try to find real institution
try {
i = m->institution("I000001");
QVERIFY(i.name() == "Inst Name");
QVERIFY(m->dirty() == false);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::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 {
m->modifyAccount(a);
QFAIL("Exception expected");
} catch (const MyMoneyException &) {
}
QVERIFY(m->dirty() == false);
// now try to do it with a real institution
try {
QVERIFY(i.accountList().count() == 0);
a.setInstitutionId(i.id());
m->modifyAccount(a);
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()
{
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 {
m->modifyAccount(a);
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(MyMoneyAccount::CreditCard);
+ d.setAccountType(Account::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 MyMoneyDatabaseMgrTest::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 {
m->modifyInstitution(i);
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 {
m->modifyInstitution(f);
QFAIL("Exception expected");
} catch (const MyMoneyException &) {
}
}
void MyMoneyDatabaseMgrTest::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(MyMoneyAccount::Expense);
+ ex1.setAccountType(Account::Expense);
MyMoneyAccount ex2;
- ex2.setAccountType(MyMoneyAccount::Expense);
+ ex2.setAccountType(Account::Expense);
MyMoneyAccount ex3;
- ex3.setAccountType(MyMoneyAccount::Expense);
+ ex3.setAccountType(Account::Expense);
MyMoneyAccount ex4;
- ex4.setAccountType(MyMoneyAccount::Expense);
+ ex4.setAccountType(Account::Expense);
MyMoneyAccount in;
- in.setAccountType(MyMoneyAccount::Income);
+ in.setAccountType(Account::Income);
MyMoneyAccount ch;
- ch.setAccountType(MyMoneyAccount::Checkings);
+ ch.setAccountType(Account::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 {
m->addAccount(ex1);
m->addAccount(ex2);
m->addAccount(ex3);
m->addAccount(ex4);
m->addAccount(in);
m->addAccount(ch);
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();
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);
QVERIFY(ch.value("Key") == "Value");
MyMoneyFile::instance()->preloadCache();
QVERIFY(m->expense().accountCount() == 3);
QVERIFY(m->account(ex1.id()).accountCount() == 1);
QVERIFY(ex3.parentAccountId() == STD_ACC_EXPENSE);
//for (int i = 0; i < 100; ++i) {
m->reparentAccount(ex3, ex1);
//}
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()
{
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.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->setDirty();
try {
ch = m->account("A000006");
QVERIFY(ch.value("Key") == "Value");
m->addTransaction(t1);
QVERIFY(t1.id() == "T000000000000000001");
QVERIFY(t1.splitCount() == 2);
QVERIFY(m->transactionCount() == 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.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.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.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.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->setDirty();
try {
m->addTransaction(t2);
QVERIFY(t2.id() == "T000000000000000002");
QVERIFY(t2.splitCount() == 4);
QVERIFY(m->transactionCount() == 2);
//QMap<QString, QString>::ConstIterator it_k;
MyMoneyTransactionFilter f;
QList<MyMoneyTransaction> transactionList(m->transactionList(f));
QList<MyMoneyTransaction>::ConstIterator it_t(transactionList.constBegin());
QCOMPARE((*it_t).id(), QLatin1String("T000000000000000002"));
++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<MyMoneyTransaction> list;
MyMoneyTransactionFilter filter("A000006");
list = m->transactionList(filter);
QCOMPARE(list.size(), 2);
QList<MyMoneyTransaction>::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()
{
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()
{
testAttachDb();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
MyMoneyBudget budget;
budget.setName("TestBudget");
budget.setBudgetStart(QDate::currentDate());
m->addBudget(budget);
QCOMPARE(m->budgetList().count(), 1);
QCOMPARE(m->budgetId(), 1ul);
MyMoneyBudget newBudget = m->budgetByName("TestBudget");
QCOMPARE(budget.budgetStart(), newBudget.budgetStart());
QCOMPARE(budget.name(), newBudget.name());
}
void MyMoneyDatabaseMgrTest::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()));
m->addBudget(newBudget);
QCOMPARE(m->budgetList().count(), 2);
QCOMPARE(m->budgetId(), 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()
{
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));
m->modifyBudget(budget);
MyMoneyBudget newBudget = m->budgetByName("TestBudget");
QCOMPARE(budget.id(), newBudget.id());
QCOMPARE(budget.budgetStart(), newBudget.budgetStart());
QCOMPARE(budget.name(), newBudget.name());
}
void MyMoneyDatabaseMgrTest::testRemoveBudget()
{
testAddBudget();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
MyMoneyBudget budget = m->budgetByName("TestBudget");
m->removeBudget(budget);
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()
{
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.setId(QString()); // 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));
m->addTransaction(t1);
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()
{
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));
m->modifyTransaction(t);
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");
m->modifyTransaction(t);
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<QString, QString>::ConstIterator it_k;
MyMoneyTransactionFilter f;
QList<MyMoneyTransaction> transactionList(m->transactionList(f));
QList<MyMoneyTransaction>::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;
//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<MyMoneyTransaction> list;
MyMoneyTransactionFilter filter("A000006");
list = m->transactionList(filter);
QVERIFY(list.size() == 2);
QList<MyMoneyTransaction>::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.setId(QString()); // 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.setId(QString()); // 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
m->addTransaction(t1);
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");
m->modifyTransaction(t1);
// 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()
{
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 {
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 {
MyMoneyFile::instance()->preloadCache();
i = m->institution("I000001");
//QVERIFY(i.accountCount() == 0);
QVERIFY(i.accountCount() == 1);
QVERIFY(m->accountCount() == 7);
a.setInstitutionId(QString());
m->modifyAccount(a);
m->removeAccount(a);
QVERIFY(m->accountCount() == 6);
i = m->institution("I000001");
QVERIFY(i.accountCount() == 0);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::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()
{
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());
m->modifyAccount(a);
QVERIFY(i.accountCount() == 0);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
m->setDirty();
// now remove the institution and see if the account survived ;-)
try {
m->removeInstitution(i);
a.setInstitutionId(QString());
m->modifyAccount(a);
a = m->account("A000006");
QVERIFY(a.institutionId().isEmpty());
QVERIFY(m->institutionCount() == 0);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::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 {
m->removeTransaction(t);
QVERIFY(m->transactionCount() == 1);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::testTransactionList()
{
testAttachDb();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
testAddTransactions();
QList<MyMoneyTransaction> 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");
}
void MyMoneyDatabaseMgrTest::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);
m->addPayee(p);
QVERIFY(m->payeeId() == 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()
{
testAttachDb();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
try {
m->setAccountName(STD_ACC_LIABILITY, "Verbindlichkeiten");
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
try {
m->setAccountName(STD_ACC_ASSET, "Verm�gen");
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
try {
m->setAccountName(STD_ACC_EXPENSE, "Ausgaben");
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
try {
m->setAccountName(STD_ACC_INCOME, "Einnahmen");
} 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 {
m->setAccountName("A000001", "New account name");
QFAIL("Exception expected");
} catch (const MyMoneyException &) {
}
}
void MyMoneyDatabaseMgrTest::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 {
m->modifyPayee(p);
p = m->payee("P000001");
QVERIFY(p.name() == "New name");
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::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);
m->removePayee(p);
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 {
m->modifyTransaction(tr);
QFAIL("Expected exception");
} catch (const MyMoneyException &) {
}
// reset here, so that the
// testAddPayee will not fail
m->loadPayeeId(0);
testAddPayee();
// check that it works when the payee exists
try {
m->modifyTransaction(tr);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
m->setDirty();
// now check, that we cannot remove the payee
try {
m->removePayee(p);
QFAIL("Expected exception");
} catch (const MyMoneyException &) {
}
QVERIFY(m->payeeList().count() == 1);
}
void MyMoneyDatabaseMgrTest::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);
m->addTag(ta);
QVERIFY(m->tagId() == 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()
{
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 {
m->modifyTag(ta);
ta = m->tag("G000001");
QVERIFY(ta.name() == "New name");
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::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);
m->removeTag(ta);
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<QString> 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 &) {
}
// reset here, so that the
// testAddTag will not fail
m->loadTagId(0);
testAddTag();
// check that it works when the tag exists
try {
m->modifyTransaction(tr);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
m->setDirty();
// now check, that we cannot remove the tag
try {
m->removeTag(ta);
QFAIL("Expected exception");
} catch (const MyMoneyException &) {
}
QVERIFY(m->tagList().count() == 1);
}
void MyMoneyDatabaseMgrTest::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 {
m->addAccount(a);
m->addAccount(b);
m->addAccount(c);
m->reparentAccount(b, a);
m->reparentAccount(c, b);
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);
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 &) {
}
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()
{
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()
{
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 &) {
}
}
void MyMoneyDatabaseMgrTest::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;
testEquality(&test);
}
void MyMoneyDatabaseMgrTest::testEquality(const MyMoneyDatabaseMgr *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->m_creationDate == t->m_creationDate);
QVERIFY(m->m_lastModificationDate == t->m_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());
}
void MyMoneyDatabaseMgrTest::testDuplicate()
{
testAttachDb();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
const MyMoneyDatabaseMgr* t;
testModifyTransaction();
t = m->duplicate();
testEquality(t);
delete t;
}
void MyMoneyDatabaseMgrTest::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().count() == 0);
MyMoneyTransaction t1;
MyMoneySplit s1, s2;
s1.setAccountId("A000001");
t1.addSplit(s1);
s2.setAccountId("A000002");
t1.addSplit(s2);
MyMoneySchedule schedule("Sched-Name",
- MyMoneySchedule::TYPE_DEPOSIT,
- MyMoneySchedule::OCCUR_DAILY, 1,
- MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ 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);
QVERIFY(m->scheduleList().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",
- MyMoneySchedule::TYPE_DEPOSIT,
- MyMoneySchedule::OCCUR_DAILY, 1,
- MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ Schedule::Type::Deposit,
+ Schedule::Occurrence::Daily, 1,
+ Schedule::PaymentType::ManualDeposit,
QDate(),
QDate(),
true,
false);
m->addSchedule(schedule);
QFAIL("Exception expected");
} catch (const MyMoneyException &) {
}
QVERIFY(m->scheduleList().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",
- MyMoneySchedule::TYPE_DEPOSIT,
- MyMoneySchedule::OCCUR_DAILY, 1,
- MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ 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);
QFAIL("Exception expected, but not thrown");
} catch (const MyMoneyException &) {
// Exception caught as expected.
}
QVERIFY(m->scheduleList().count() == 1);
}
void MyMoneyDatabaseMgrTest::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");
QFAIL("Exception expected");
} catch (const MyMoneyException &) {
}
}
void MyMoneyDatabaseMgrTest::testModifySchedule()
{
testAttachDb();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
testAddSchedule();
MyMoneySchedule sched;
sched = m->schedule("SCH000001");
sched.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);
QVERIFY(m->scheduleList().count() == 1);
QVERIFY((*(m->scheduleList().begin())).name() == "New Sched-Name");
QVERIFY((*(m->scheduleList().begin())).id() == "SCH000001");
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::testRemoveSchedule()
{
testAttachDb();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
testAddSchedule();
MyMoneySchedule sched;
sched = m->schedule("SCH000001");
sched.setId("SCH000002");
try {
m->removeSchedule(sched);
QFAIL("Exception expected");
} catch (const MyMoneyException &) {
}
sched = m->schedule("SCH000001");
try {
m->removeSchedule(sched);
QVERIFY(m->scheduleList().count() == 0);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::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",
- MyMoneySchedule::TYPE_BILL,
- MyMoneySchedule::OCCUR_ONCE, 1,
- MyMoneySchedule::STYPE_DIRECTDEBIT,
+ 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",
- MyMoneySchedule::TYPE_DEPOSIT,
- MyMoneySchedule::OCCUR_DAILY, 1,
- MyMoneySchedule::STYPE_DIRECTDEPOSIT,
+ 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",
- MyMoneySchedule::TYPE_TRANSFER,
- MyMoneySchedule::OCCUR_WEEKLY, 1,
- MyMoneySchedule::STYPE_OTHER,
+ 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",
- MyMoneySchedule::TYPE_BILL,
- MyMoneySchedule::OCCUR_WEEKLY, 1,
- MyMoneySchedule::STYPE_WRITECHEQUE,
+ 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) {
unexpectedException(e);
}
QList<MyMoneySchedule> list;
// no filter
list = m->scheduleList();
QVERIFY(list.count() == 4);
// filter by type
- list = m->scheduleList("", MyMoneySchedule::TYPE_BILL);
+ list = m->scheduleList(QString(), Schedule::Type::Bill);
QVERIFY(list.count() == 2);
QVERIFY(list[0].name() == "Schedule 1");
QVERIFY(list[1].name() == "Schedule 4");
// filter by occurrence
- list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_DAILY);
+ list = m->scheduleList(QString(), Schedule::Type::Any,
+ Schedule::Occurrence::Daily);
QVERIFY(list.count() == 1);
QVERIFY(list[0].name() == "Schedule 2");
// filter by payment type
- list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_DIRECTDEPOSIT);
+ list = m->scheduleList(QString(), Schedule::Type::Any,
+ Schedule::Occurrence::Any,
+ Schedule::PaymentType::DirectDeposit);
QVERIFY(list.count() == 1);
QVERIFY(list[0].name() == "Schedule 2");
// filter by account
list = m->scheduleList("A01");
QVERIFY(list.count() == 0);
list = m->scheduleList("A000001");
QVERIFY(list.count() == 2);
list = m->scheduleList("A000002");
QVERIFY(list.count() == 1);
// filter by start date
- list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_ANY,
- notOverdue.addDays(31));
+ 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("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_ANY,
+ list = m->scheduleList(QString(), Schedule::Type::Any,
+ Schedule::Occurrence::Any,
+ Schedule::PaymentType::Any,
QDate(),
- notOverdue.addDays(1));
+ 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("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_ANY,
+ list = m->scheduleList(QString(), Schedule::Type::Any,
+ Schedule::Occurrence::Any,
+ Schedule::PaymentType::Any,
notOverdue.addDays(-1),
- 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("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_ANY,
+ 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()
{
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 {
m->addCurrency(curr);
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 {
m->addCurrency(curr);
QFAIL("Expected exception missing");
} catch (const MyMoneyException &) {
QVERIFY(m->dirty() == false);
}
}
void MyMoneyDatabaseMgrTest::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 {
m->modifyCurrency(curr);
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);
}
}
void MyMoneyDatabaseMgrTest::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 {
m->removeCurrency(curr);
QVERIFY(m->currencyList().count() == 0);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
m->setDirty();
MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100);
try {
m->removeCurrency(unknownCurr);
QFAIL("Expected exception missing");
} catch (const MyMoneyException &) {
QVERIFY(m->dirty() == false);
}
}
void MyMoneyDatabaseMgrTest::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(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);
}
}
void MyMoneyDatabaseMgrTest::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 {
m->addCurrency(unknownCurr);
m->setDirty();
QVERIFY(m->currencyList().count() == 2);
QVERIFY(m->currencyList().count() == 2);
QVERIFY(m->dirty() == false);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
}
void MyMoneyDatabaseMgrTest::testAccountList()
{
testAttachDb();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
QList<MyMoneyAccount> 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");
m->reparentAccount(b, a);
accounts.clear();
m->accountList(accounts);
QVERIFY(accounts.count() == 2);
}
void MyMoneyDatabaseMgrTest::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 {
m->addOnlineJob(job);
QFAIL("Expected exception missing");
} catch (const MyMoneyException &) {
QCOMPARE(m->dirty(), false);
}
}
void MyMoneyDatabaseMgrTest::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 {
m->modifyOnlineJob(job);
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 {
m->modifyOnlineJob(unknownJob);
QFAIL("Expected exception missing");
} catch (const MyMoneyException &) {
QVERIFY(m->dirty() == false);
}
}
void MyMoneyDatabaseMgrTest::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 {
m->removeOnlineJob(job);
QVERIFY(m->onlineJobList().count() == 0);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
m->setDirty();
onlineJob unknownJob(new dummyTask());
try {
m->removeOnlineJob(unknownJob);
QFAIL("Expected exception missing");
} catch (const MyMoneyException &) {
QVERIFY(m->dirty() == false);
}
}
void MyMoneyDatabaseMgrTest::testHighestNumberFromIdString()
{
testAttachDb();
if (!m_canOpen)
QSKIP("Database test skipped because no database could be opened.", SkipAll);
testAddTransactions();
QCOMPARE(m->m_sql->highestNumberFromIdString(QLatin1String("kmmTransactions"), QLatin1String("id"), 1), 2ul);
QCOMPARE(m->m_sql->highestNumberFromIdString(QLatin1String("kmmAccounts"), QLatin1String("id"), 1), 6ul);
}
diff --git a/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.cpp b/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.cpp
index f414bf46f..f36c1cca3 100644
--- a/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.cpp
+++ b/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.cpp
@@ -1,1804 +1,1808 @@
/***************************************************************************
mymoneyseqaccessmgrtest.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 <iostream>
#include <QList>
#include <QtTest/QtTest>
#include "mymoneytestutils.h"
#include "mymoneyfile.h"
#include "onlinetasks/dummy/tasks/dummytask.h"
+#include "mymoneyenums.h"
+
+using namespace eMyMoney;
+
QTEST_GUILESS_MAIN(MyMoneySeqAccessMgrTest)
void MyMoneySeqAccessMgrTest::init()
{
m = new MyMoneySeqAccessMgr;
MyMoneyFile* file = MyMoneyFile::instance();
file->attachStorage(m);
m->startTransaction();
}
void MyMoneySeqAccessMgrTest::cleanup()
{
m->commitTransaction();
MyMoneyFile* file = MyMoneyFile::instance();
file->detachStorage(m);
delete m;
}
void MyMoneySeqAccessMgrTest::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->m_nextInstitutionID, 0ul);
QCOMPARE(m->m_nextAccountID, 0ul);
QCOMPARE(m->m_nextTransactionID, 0ul);
QCOMPARE(m->m_nextPayeeID, 0ul);
QCOMPARE(m->m_nextScheduleID, 0ul);
QCOMPARE(m->m_nextReportID, 0ul);
QCOMPARE(m->m_institutionList.count(), 0);
QCOMPARE(m->m_accountList.count(), 5);
QCOMPARE(m->m_transactionList.count(), 0);
QCOMPARE(m->m_transactionKeys.count(), 0);
QCOMPARE(m->m_payeeList.count(), 0);
QCOMPARE(m->m_tagList.count(), 0);
QCOMPARE(m->m_scheduleList.count(), 0);
QCOMPARE(m->m_dirty, false);
QCOMPARE(m->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()
{
MyMoneyPayee user = m->user();
m->m_dirty = false;
user.setName("Name");
m->setUser(user);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->dirty(), true);
m->m_dirty = false;
user.setAddress("Street");
m->setUser(user);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->dirty(), true);
m->m_dirty = false;
user.setCity("Town");
m->setUser(user);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->dirty(), true);
m->m_dirty = false;
user.setState("County");
m->setUser(user);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->dirty(), true);
m->m_dirty = false;
user.setPostcode("Postcode");
m->setUser(user);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->dirty(), true);
m->m_dirty = false;
user.setTelephone("Telephone");
m->setUser(user);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->dirty(), true);
m->m_dirty = false;
user.setEmail("Email");
m->setUser(user);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->dirty(), true);
m->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->m_dirty = false;
m->deletePair("key");
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->dirty(), true);
}
void MyMoneySeqAccessMgrTest::testSupportFunctions()
{
QCOMPARE(m->nextInstitutionID(), QLatin1String("I000001"));
QCOMPARE(m->m_nextInstitutionID, 1ul);
QCOMPARE(m->nextAccountID(), QLatin1String("A000001"));
QCOMPARE(m->m_nextAccountID, 1ul);
QCOMPARE(m->nextTransactionID(), QLatin1String("T000000000000000001"));
QCOMPARE(m->m_nextTransactionID, 1ul);
QCOMPARE(m->nextPayeeID(), QLatin1String("P000001"));
QCOMPARE(m->m_nextPayeeID, 1ul);
QCOMPARE(m->nextTagID(), QLatin1String("G000001"));
QCOMPARE(m->m_nextTagID, 1ul);
QCOMPARE(m->nextScheduleID(), QLatin1String("SCH000001"));
QCOMPARE(m->m_nextScheduleID, 1ul);
QCOMPARE(m->nextReportID(), QLatin1String("R000001"));
QCOMPARE(m->m_nextReportID, 1ul);
QCOMPARE(m->nextOnlineJobID(), QLatin1String("O000001"));
QCOMPARE(m->m_nextOnlineJobID, 1ul);
}
void MyMoneySeqAccessMgrTest::testIsStandardAccount()
{
QCOMPARE(m->isStandardAccount(STD_ACC_LIABILITY), true);
QCOMPARE(m->isStandardAccount(STD_ACC_ASSET), true);
QCOMPARE(m->isStandardAccount(STD_ACC_EXPENSE), true);
QCOMPARE(m->isStandardAccount(STD_ACC_INCOME), true);
QCOMPARE(m->isStandardAccount("A0002"), false);
}
void MyMoneySeqAccessMgrTest::testNewAccount()
{
MyMoneyAccount a;
a.setName("AccountName");
a.setNumber("AccountNumber");
m->addAccount(a);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->m_nextAccountID, 1ul);
QCOMPARE(m->dirty(), true);
QCOMPARE(m->m_accountList.count(), 6);
QCOMPARE(m->m_accountList["A000001"].name(), QLatin1String("AccountName"));
}
void MyMoneySeqAccessMgrTest::testAccount()
{
testNewAccount();
m->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()
{
testNewAccount();
MyMoneyAccount a, b;
b.setName("Account2");
b.setNumber("Acc2");
m->addAccount(b);
m->commitTransaction();
m->startTransaction();
m->m_dirty = false;
QCOMPARE(m->m_nextAccountID, 2ul);
QCOMPARE(m->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->m_accountList[STD_ACC_ASSET].accountList().count(), 0);
m->addAccount(b, a);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->m_accountList["A000002"].accountList()[0], QLatin1String("A000001"));
QCOMPARE(m->m_accountList["A000002"].accountList().count(), 1);
QCOMPARE(m->m_accountList[STD_ACC_ASSET].accountList().count(), 0);
QCOMPARE(m->dirty(), true);
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
}
void MyMoneySeqAccessMgrTest::testAddInstitution()
{
MyMoneyInstitution i;
i.setName("Inst Name");
m->addInstitution(i);
QCOMPARE(m->m_institutionList.count(), 1);
QCOMPARE(m->m_nextInstitutionID, 1ul);
QCOMPARE(m->m_institutionList["I000001"].name(), QLatin1String("Inst Name"));
}
void MyMoneySeqAccessMgrTest::testInstitution()
{
testAddInstitution();
MyMoneyInstitution i;
m->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()
{
testAddInstitution();
testAddNewAccount();
MyMoneyInstitution i;
MyMoneyAccount a, b;
try {
i = m->institution("I000001");
a = m->account("A000001");
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
m->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()
{
testAccount2Institution();
// test the OK case
MyMoneyAccount a = m->account("A000001");
a.setName("New account name");
m->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->m_dirty = false;
try {
m->modifyAccount(c);
QFAIL("Exception expected");
} catch (const MyMoneyException &) {
}
// use different account type
MyMoneyAccount d;
- d.setAccountType(MyMoneyAccount::CreditCard);
+ d.setAccountType(Account::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()
{
testAddInstitution();
MyMoneyInstitution i = m->institution("I000001");
m->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()
{
// this one adds some accounts to the database
MyMoneyAccount ex1;
- ex1.setAccountType(MyMoneyAccount::Expense);
+ ex1.setAccountType(Account::Expense);
MyMoneyAccount ex2;
- ex2.setAccountType(MyMoneyAccount::Expense);
+ ex2.setAccountType(Account::Expense);
MyMoneyAccount ex3;
- ex3.setAccountType(MyMoneyAccount::Expense);
+ ex3.setAccountType(Account::Expense);
MyMoneyAccount ex4;
- ex4.setAccountType(MyMoneyAccount::Expense);
+ ex4.setAccountType(Account::Expense);
MyMoneyAccount in;
- in.setAccountType(MyMoneyAccount::Income);
+ in.setAccountType(Account::Income);
MyMoneyAccount ch;
- ch.setAccountType(MyMoneyAccount::Checkings);
+ ch.setAccountType(Account::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(), QLatin1String(STD_ACC_EXPENSE));
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()
{
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.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->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(), 1u);
} catch (const MyMoneyException &e) {
unexpectedException(e);
}
try {
// I spent some money, not so great
s.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.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.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.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->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(), 2u);
QMap<QString, QString>::ConstIterator it_k;
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
it_k = m->m_transactionKeys.begin();
it_t = m->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->m_transactionKeys.end());
QCOMPARE(it_t, m->m_transactionList.end());
ch = m->account("A000006");
// check that the account's transaction list is updated
QList<MyMoneyTransaction> list;
MyMoneyTransactionFilter filter("A000006");
list = m->transactionList(filter);
QCOMPARE(list.size(), 2);
QList<MyMoneyTransaction>::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()
{
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()
{
testAddTransactions();
QVERIFY(m->balance("A000001").isZero());
QCOMPARE(m->balance("A000002"), MyMoneyMoney(1200, 100));
QCOMPARE(m->balance("A000003"), MyMoneyMoney(400, 100));
QCOMPARE(m->totalBalance("A000001"), 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()
{
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"), MyMoneyMoney(10000, 100));
QCOMPARE(m->balance("A000006"), MyMoneyMoney(100000 - 11600, 100));
QCOMPARE(m->totalBalance("A000001"), MyMoneyMoney(1600, 100));
m->modifyTransaction(t);
QCOMPARE(m->balance("A000004"), MyMoneyMoney(11000, 100));
QCOMPARE(m->balance("A000006"), MyMoneyMoney(100000 - 12600, 100));
QCOMPARE(m->totalBalance("A000001"), 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"), MyMoneyMoney(11000, 100));
QCOMPARE(m->balance("A000006"), MyMoneyMoney(100000 - 12600, 100));
QCOMPARE(m->totalBalance("A000001"), MyMoneyMoney(1600, 100));
QMap<QString, QString>::ConstIterator it_k;
QMap<QString, MyMoneyTransaction>::ConstIterator it_t;
it_k = m->m_transactionKeys.begin();
it_t = m->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->m_transactionKeys.end());
QCOMPARE(it_t, m->m_transactionList.end());
ch = m->account("A000006");
// check that the account's transaction list is updated
QList<MyMoneyTransaction> list;
MyMoneyTransactionFilter filter("A000006");
list = m->transactionList(filter);
QCOMPARE(list.size(), 2);
QList<MyMoneyTransaction>::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()
{
testAccount2Institution();
MyMoneyAccount a = m->account("A000001");
MyMoneyInstitution i = m->institution("I000001");
m->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()
{
testAddTransactions();
MyMoneyAccount a = m->account("A000006");
try {
m->removeAccount(a);
QFAIL("Exception expected");
} catch (const MyMoneyException &) {
}
}
void MyMoneySeqAccessMgrTest::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->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()
{
testAddTransactions();
MyMoneyTransaction t = m->transaction("T000000000000000002");
m->m_dirty = false;
try {
m->removeTransaction(t);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->dirty(), true);
QCOMPARE(m->transactionCount(), 1u);
/* removed with MyMoneyAccount::Transaction
QCOMPARE(m->account("A000006").transactionCount(), 1);
*/
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
}
void MyMoneySeqAccessMgrTest::testTransactionList()
{
testAddTransactions();
QList<MyMoneyTransaction> 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()
{
MyMoneyPayee p;
p.setName("THB");
m->m_dirty = false;
try {
QCOMPARE(m->m_nextPayeeID, 0ul);
m->addPayee(p);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->dirty(), true);
QCOMPARE(m->m_nextPayeeID, 1ul);
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
}
void MyMoneySeqAccessMgrTest::testSetAccountName()
{
try {
m->setAccountName(STD_ACC_LIABILITY, "Verbindlichkeiten");
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
try {
m->setAccountName(STD_ACC_ASSET, QString("Vermögen"));
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
try {
m->setAccountName(STD_ACC_EXPENSE, "Ausgaben");
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
try {
m->setAccountName(STD_ACC_INCOME, "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()
{
MyMoneyPayee p;
testAddPayee();
p = m->payee("P000001");
p.setName("New name");
m->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()
{
testAddPayee();
m->m_dirty = false;
// check that we can remove an unreferenced payee
MyMoneyPayee p = m->payee("P000001");
try {
QCOMPARE(m->m_payeeList.count(), 1);
m->removePayee(p);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->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->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->m_dirty = false;
// now check, that we cannot remove the payee
try {
m->removePayee(p);
QFAIL("Expected exception");
} catch (const MyMoneyException &) {
}
QCOMPARE(m->m_payeeList.count(), 1);
}
void MyMoneySeqAccessMgrTest::testAddTag()
{
MyMoneyTag ta;
ta.setName("THB");
m->m_dirty = false;
try {
QCOMPARE(m->m_nextTagID, 0ul);
m->addTag(ta);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->dirty(), true);
QCOMPARE(m->m_nextTagID, 1ul);
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
}
void MyMoneySeqAccessMgrTest::testModifyTag()
{
MyMoneyTag ta;
testAddTag();
ta = m->tag("G000001");
ta.setName("New name");
m->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()
{
testAddTag();
m->m_dirty = false;
// check that we can remove an unreferenced tag
MyMoneyTag ta = m->tag("G000001");
try {
QCOMPARE(m->m_tagList.count(), 1);
m->removeTag(ta);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->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<QString> 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->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->m_dirty = false;
// now check, that we cannot remove the tag
try {
m->removeTag(ta);
QFAIL("Expected exception");
} catch (const MyMoneyException &) {
}
QCOMPARE(m->m_tagList.count(), 1);
}
void MyMoneySeqAccessMgrTest::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()
{
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()
{
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 &) {
}
}
void MyMoneySeqAccessMgrTest::testAssignment()
{
testAddTransactions();
MyMoneyPayee user;
user.setName("Thomas");
m->setUser(user);
MyMoneySeqAccessMgr test = *m;
testEquality(&test);
}
void MyMoneySeqAccessMgrTest::testEquality(const MyMoneySeqAccessMgr *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->m_nextInstitutionID, t->m_nextInstitutionID);
QCOMPARE(m->m_nextAccountID, t->m_nextAccountID);
QCOMPARE(m->m_nextTransactionID, t->m_nextTransactionID);
QCOMPARE(m->m_nextPayeeID, t->m_nextPayeeID);
QCOMPARE(m->m_nextTagID, t->m_nextTagID);
QCOMPARE(m->m_nextScheduleID, t->m_nextScheduleID);
QCOMPARE(m->m_dirty, t->m_dirty);
QCOMPARE(m->m_creationDate, t->m_creationDate);
QCOMPARE(m->m_lastModificationDate, t->m_lastModificationDate);
/*
* make sure, that the keys and values are the same
* on the left and the right side
*/
QCOMPARE(m->m_payeeList.keys(), t->m_payeeList.keys());
QCOMPARE(m->m_payeeList.values(), t->m_payeeList.values());
QCOMPARE(m->m_tagList.keys(), t->m_tagList.keys());
QCOMPARE(m->m_tagList.values(), t->m_tagList.values());
QCOMPARE(m->m_transactionKeys.keys(), t->m_transactionKeys.keys());
QCOMPARE(m->m_transactionKeys.values(), t->m_transactionKeys.values());
QCOMPARE(m->m_institutionList.keys(), t->m_institutionList.keys());
QCOMPARE(m->m_institutionList.values(), t->m_institutionList.values());
QCOMPARE(m->m_accountList.keys(), t->m_accountList.keys());
QCOMPARE(m->m_accountList.values(), t->m_accountList.values());
QCOMPARE(m->m_transactionList.keys(), t->m_transactionList.keys());
QCOMPARE(m->m_transactionList.values(), t->m_transactionList.values());
// QCOMPARE(m->m_scheduleList.keys(), t->m_scheduleList.keys());
// QCOMPARE(m->m_scheduleList.values(), t->m_scheduleList.values());
}
void MyMoneySeqAccessMgrTest::testDuplicate()
{
const MyMoneySeqAccessMgr* t;
testModifyTransaction();
t = m->duplicate();
testEquality(t);
delete t;
}
void MyMoneySeqAccessMgrTest::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->m_scheduleList.count(), 0);
MyMoneyTransaction t1;
MyMoneySplit s1, s2;
s1.setAccountId("A000001");
t1.addSplit(s1);
s2.setAccountId("A000002");
t1.addSplit(s2);
MyMoneySchedule schedule("Sched-Name",
- MyMoneySchedule::TYPE_DEPOSIT,
- MyMoneySchedule::OCCUR_DAILY, 1,
- MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ 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->m_scheduleList.count(), 1);
QCOMPARE(schedule.id(), QLatin1String("SCH000001"));
QCOMPARE(m->m_scheduleList["SCH000001"].id(), QLatin1String("SCH000001"));
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
try {
MyMoneySchedule schedule("Sched-Name",
- MyMoneySchedule::TYPE_DEPOSIT,
- MyMoneySchedule::OCCUR_DAILY, 1,
- MyMoneySchedule::STYPE_MANUALDEPOSIT,
+ 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()
{
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()
{
testAddSchedule();
MyMoneySchedule sched;
sched = m->schedule("SCH000001");
sched.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->m_scheduleList.count(), 1);
QCOMPARE(m->m_scheduleList["SCH000001"].name(), QLatin1String("New Sched-Name"));
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
}
void MyMoneySeqAccessMgrTest::testRemoveSchedule()
{
testAddSchedule();
m->commitTransaction();
m->startTransaction();
MyMoneySchedule sched;
sched = m->schedule("SCH000001");
sched.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->m_scheduleList.count(), 0);
} catch (const MyMoneyException &) {
m->rollbackTransaction();
QFAIL("Unexpected exception");
}
m->startTransaction();
}
void MyMoneySeqAccessMgrTest::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);
MyMoneySchedule schedule1("Schedule 1",
- MyMoneySchedule::TYPE_BILL,
- MyMoneySchedule::OCCUR_ONCE, 1,
- MyMoneySchedule::STYPE_DIRECTDEBIT,
+ 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",
- MyMoneySchedule::TYPE_DEPOSIT,
- MyMoneySchedule::OCCUR_DAILY, 1,
- MyMoneySchedule::STYPE_DIRECTDEPOSIT,
+ 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",
- MyMoneySchedule::TYPE_TRANSFER,
- MyMoneySchedule::OCCUR_WEEKLY, 1,
- MyMoneySchedule::STYPE_OTHER,
+ 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",
- MyMoneySchedule::TYPE_BILL,
- MyMoneySchedule::OCCUR_WEEKLY, 1,
- MyMoneySchedule::STYPE_WRITECHEQUE,
+ 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<MyMoneySchedule> list;
// no filter
list = m->scheduleList();
QCOMPARE(list.count(), 4);
// filter by type
- list = m->scheduleList("", MyMoneySchedule::TYPE_BILL);
+ list = m->scheduleList("", Schedule::Type::Bill);
QCOMPARE(list.count(), 2);
QCOMPARE(list[0].name(), QLatin1String("Schedule 1"));
QCOMPARE(list[1].name(), QLatin1String("Schedule 4"));
// filter by occurrence
- list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_DAILY);
+ list = m->scheduleList("", Schedule::Type::Any,
+ Schedule::Occurrence::Daily);
QCOMPARE(list.count(), 1);
QCOMPARE(list[0].name(), QLatin1String("Schedule 2"));
// filter by payment type
- list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_DIRECTDEPOSIT);
+ list = m->scheduleList("", Schedule::Type::Any,
+ Schedule::Occurrence::Any,
+ Schedule::PaymentType::DirectDeposit);
QCOMPARE(list.count(), 1);
QCOMPARE(list[0].name(), QLatin1String("Schedule 2"));
// filter by account
list = m->scheduleList("A01");
QCOMPARE(list.count(), 0);
list = m->scheduleList("A000001");
QCOMPARE(list.count(), 2);
list = m->scheduleList("A000002");
QCOMPARE(list.count(), 1);
// filter by start date
- list = m->scheduleList("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_ANY,
+ list = m->scheduleList("", Schedule::Type::Any,
+ Schedule::Occurrence::Any,
+ Schedule::PaymentType::Any,
notOverdue.addDays(31));
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("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_ANY,
+ list = m->scheduleList("", Schedule::Type::Any,
+ Schedule::Occurrence::Any,
+ Schedule::PaymentType::Any,
QDate(),
notOverdue.addDays(1));
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("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_ANY,
+ list = m->scheduleList("", Schedule::Type::Any,
+ Schedule::Occurrence::Any,
+ Schedule::PaymentType::Any,
notOverdue.addDays(-1),
notOverdue.addDays(1));
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("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_ANY,
+ 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()
{
MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
QCOMPARE(m->m_currencyList.count(), 0);
m->m_dirty = false;
try {
m->addCurrency(curr);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->m_currencyList.count(), 1);
QCOMPARE(m->m_currencyList["EUR"].name(), QLatin1String("Euro"));
QCOMPARE(m->dirty(), true);
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
m->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()
{
MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
testAddCurrency();
m->m_dirty = false;
curr.setName("EURO");
try {
m->modifyCurrency(curr);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->m_currencyList.count(), 1);
QCOMPARE(m->m_currencyList["EUR"].name(), QLatin1String("EURO"));
QCOMPARE(m->dirty(), true);
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
m->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()
{
MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
testAddCurrency();
m->m_dirty = false;
try {
m->removeCurrency(curr);
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->m_currencyList.count(), 0);
QCOMPARE(m->dirty(), true);
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
m->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()
{
MyMoneySecurity curr("EUR", "Euro", "?", 100, 100);
MyMoneySecurity newCurr;
testAddCurrency();
m->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()
{
QCOMPARE(m->currencyList().count(), 0);
testAddCurrency();
QCOMPARE(m->currencyList().count(), 1);
MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100);
try {
m->addCurrency(unknownCurr);
m->m_dirty = false;
QCOMPARE(m->m_currencyList.count(), 2);
QCOMPARE(m->currencyList().count(), 2);
QCOMPARE(m->dirty(), false);
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
}
void MyMoneySeqAccessMgrTest::testAccountList()
{
QList<MyMoneyAccount> 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()
{
// we don't need the transaction started by setup() here
m->rollbackTransaction();
// account loader
QMap<QString, MyMoneyAccount> amap;
MyMoneyAccount acc("A0000176", MyMoneyAccount());
amap[acc.id()] = acc;
m->loadAccounts(amap);
QCOMPARE(m->m_accountList.values(), amap.values());
QCOMPARE(m->m_accountList.keys(), amap.keys());
QCOMPARE(m->m_nextAccountID, 176ul);
// transaction loader
QMap<QString, MyMoneyTransaction> tmap;
MyMoneyTransaction t("T000000108", MyMoneyTransaction());
tmap[t.id()] = t;
m->loadTransactions(tmap);
QCOMPARE(m->m_transactionList.values(), tmap.values());
QCOMPARE(m->m_transactionList.keys(), tmap.keys());
QCOMPARE(m->m_nextTransactionID, 108ul);
// institution loader
QMap<QString, MyMoneyInstitution> imap;
MyMoneyInstitution inst("I000028", MyMoneyInstitution());
imap[inst.id()] = inst;
m->loadInstitutions(imap);
QCOMPARE(m->m_institutionList.values(), imap.values());
QCOMPARE(m->m_institutionList.keys(), imap.keys());
QCOMPARE(m->m_nextInstitutionID, 28ul);
// payee loader
QMap<QString, MyMoneyPayee> pmap;
MyMoneyPayee p("P1234", MyMoneyPayee());
pmap[p.id()] = p;
m->loadPayees(pmap);
QCOMPARE(m->m_payeeList.values(), pmap.values());
QCOMPARE(m->m_payeeList.keys(), pmap.keys());
QCOMPARE(m->m_nextPayeeID, 1234ul);
// tag loader
QMap<QString, MyMoneyTag> tamap;
MyMoneyTag ta("G1234", MyMoneyTag());
tamap[ta.id()] = ta;
m->loadTags(tamap);
QCOMPARE(m->m_tagList.values(), tamap.values());
QCOMPARE(m->m_tagList.keys(), tamap.keys());
QCOMPARE(m->m_nextTagID, 1234ul);
// security loader
QMap<QString, MyMoneySecurity> smap;
MyMoneySecurity s("S54321", MyMoneySecurity());
smap[s.id()] = s;
m->loadSecurities(smap);
QCOMPARE(m->m_securitiesList.values(), smap.values());
QCOMPARE(m->m_securitiesList.keys(), smap.keys());
QCOMPARE(m->m_nextSecurityID, 54321ul);
// schedule loader
QMap<QString, MyMoneySchedule> schmap;
MyMoneySchedule sch("SCH6789", MyMoneySchedule());
schmap[sch.id()] = sch;
m->loadSchedules(schmap);
QCOMPARE(m->m_scheduleList.values(), schmap.values());
QCOMPARE(m->m_scheduleList.keys(), schmap.keys());
QCOMPARE(m->m_nextScheduleID, 6789ul);
// report loader
QMap<QString, MyMoneyReport> rmap;
MyMoneyReport r("R1298", MyMoneyReport());
rmap[r.id()] = r;
m->loadReports(rmap);
QCOMPARE(m->m_reportList.values(), rmap.values());
QCOMPARE(m->m_reportList.keys(), rmap.keys());
QCOMPARE(m->m_nextReportID, 1298ul);
// budget loader
QMap<QString, MyMoneyBudget> bmap;
MyMoneyBudget b("B89765", MyMoneyBudget());
bmap[b.id()] = b;
m->loadBudgets(bmap);
QCOMPARE(m->m_budgetList.values(), bmap.values());
QCOMPARE(m->m_budgetList.keys(), bmap.keys());
QCOMPARE(m->m_nextBudgetID, 89765ul);
// restart a transaction so that teardown() is happy
m->startTransaction();
}
void MyMoneySeqAccessMgrTest::testAddOnlineJob()
{
// Add a onlineJob
onlineJob job(new dummyTask());
m->addOnlineJob(job);
QCOMPARE(job.id(), QString("O000001"));
m->commitTransaction();
m->startTransaction();
QCOMPARE(m->m_nextOnlineJobID, 1ul);
QCOMPARE(m->dirty(), true);
QCOMPARE(m->m_onlineJobList.count(), 1);
QVERIFY(! m->m_onlineJobList["O000001"].isNull());
}
diff --git a/kmymoney/mymoney/tests/mymoneyaccount-test.cpp b/kmymoney/mymoney/tests/mymoneyaccount-test.cpp
index d9ba7a1bc..0e61f5ef4 100644
--- a/kmymoney/mymoney/tests/mymoneyaccount-test.cpp
+++ b/kmymoney/mymoney/tests/mymoneyaccount-test.cpp
@@ -1,567 +1,567 @@
/***************************************************************************
mymoneyaccounttest.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 "mymoneyaccount-test.h"
#include <QtTest/QtTest>
#define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyAccountTest;
#include "mymoneyaccount.h"
#include "mymoneyexception.h"
#include "mymoneysplit.h"
QTEST_GUILESS_MAIN(MyMoneyAccountTest)
void MyMoneyAccountTest::init()
{
}
void MyMoneyAccountTest::cleanup()
{
}
void MyMoneyAccountTest::testEmptyConstructor()
{
MyMoneyAccount a;
QVERIFY(a.id().isEmpty());
QVERIFY(a.name().isEmpty());
- QVERIFY(a.accountType() == MyMoneyAccount::UnknownAccountType);
+ QVERIFY(a.accountType() == eMyMoney::Account::Unknown);
QVERIFY(a.openingDate() == QDate());
QVERIFY(a.lastModified() == QDate());
QVERIFY(a.lastReconciliationDate() == QDate());
QVERIFY(a.accountList().count() == 0);
QVERIFY(a.balance().isZero());
}
void MyMoneyAccountTest::testConstructor()
{
QString id = "A000001";
QString institutionid = "B000001";
QString parent = "Parent";
MyMoneyAccount r;
MyMoneySplit s;
- r.setAccountType(MyMoneyAccount::Asset);
+ r.setAccountType(eMyMoney::Account::Asset);
r.setOpeningDate(QDate::currentDate());
r.setLastModified(QDate::currentDate());
r.setDescription("Desc");
r.setNumber("465500");
r.setParentAccountId(parent);
r.setValue(QString("key"), "value");
s.setShares(MyMoneyMoney::ONE);
r.adjustBalance(s);
QVERIFY(r.m_kvp.count() == 1);
QVERIFY(r.value("key") == "value");
MyMoneyAccount a(id, r);
QVERIFY(a.id() == id);
QVERIFY(a.institutionId().isEmpty());
- QVERIFY(a.accountType() == MyMoneyAccount::Asset);
+ QVERIFY(a.accountType() == eMyMoney::Account::Asset);
QVERIFY(a.openingDate() == QDate::currentDate());
QVERIFY(a.lastModified() == QDate::currentDate());
QVERIFY(a.number() == "465500");
QVERIFY(a.description() == "Desc");
QVERIFY(a.accountList().count() == 0);
QVERIFY(a.parentAccountId() == "Parent");
QVERIFY(a.balance() == MyMoneyMoney::ONE);
QMap<QString, QString> copy;
copy = r.pairs();
QVERIFY(copy.count() == 1);
QVERIFY(copy[QString("key")] == "value");
}
void MyMoneyAccountTest::testSetFunctions()
{
MyMoneyAccount a;
QDate today(QDate::currentDate());
QVERIFY(a.name().isEmpty());
QVERIFY(a.lastModified() == QDate());
QVERIFY(a.description().isEmpty());
a.setName("Account");
a.setInstitutionId("Institution1");
a.setLastModified(today);
a.setDescription("Desc");
a.setNumber("123456");
- a.setAccountType(MyMoneyAccount::MoneyMarket);
+ a.setAccountType(eMyMoney::Account::MoneyMarket);
QVERIFY(a.name() == "Account");
QVERIFY(a.institutionId() == "Institution1");
QVERIFY(a.lastModified() == today);
QVERIFY(a.description() == "Desc");
QVERIFY(a.number() == "123456");
- QVERIFY(a.accountType() == MyMoneyAccount::MoneyMarket);
+ QVERIFY(a.accountType() == eMyMoney::Account::MoneyMarket);
}
void MyMoneyAccountTest::testCopyConstructor()
{
QString id = "A000001";
QString institutionid = "B000001";
QString parent = "ParentAccount";
MyMoneyAccount r;
- r.setAccountType(MyMoneyAccount::Expense);
+ r.setAccountType(eMyMoney::Account::Expense);
r.setOpeningDate(QDate::currentDate());
r.setLastModified(QDate::currentDate());
r.setName("Account");
r.setInstitutionId("Inst1");
r.setDescription("Desc1");
r.setNumber("Number");
r.setParentAccountId(parent);
r.setValue("Key", "Value");
MyMoneyAccount a(id, r);
a.setInstitutionId(institutionid);
MyMoneyAccount b(a);
QVERIFY(b.name() == "Account");
QVERIFY(b.institutionId() == institutionid);
- QVERIFY(b.accountType() == MyMoneyAccount::Expense);
+ QVERIFY(b.accountType() == eMyMoney::Account::Expense);
QVERIFY(b.lastModified() == QDate::currentDate());
QVERIFY(b.openingDate() == QDate::currentDate());
QVERIFY(b.description() == "Desc1");
QVERIFY(b.number() == "Number");
QVERIFY(b.parentAccountId() == "ParentAccount");
QVERIFY(b.value("Key") == "Value");
}
void MyMoneyAccountTest::testAssignmentConstructor()
{
MyMoneyAccount a;
- a.setAccountType(MyMoneyAccount::Checkings);
+ a.setAccountType(eMyMoney::Account::Checkings);
a.setName("Account");
a.setInstitutionId("Inst1");
a.setDescription("Bla");
a.setNumber("assigned Number");
a.setValue("Key", "Value");
a.addAccountId("ChildAccount");
MyMoneyAccount b;
b.setLastModified(QDate::currentDate());
b = a;
QVERIFY(b.name() == "Account");
QVERIFY(b.institutionId() == "Inst1");
- QVERIFY(b.accountType() == MyMoneyAccount::Checkings);
+ QVERIFY(b.accountType() == eMyMoney::Account::Checkings);
QVERIFY(b.lastModified() == QDate());
QVERIFY(b.openingDate() == a.openingDate());
QVERIFY(b.description() == "Bla");
QVERIFY(b.number() == "assigned Number");
QVERIFY(b.value("Key") == "Value");
QVERIFY(b.accountList().count() == 1);
QVERIFY(b.accountList()[0] == "ChildAccount");
}
void MyMoneyAccountTest::testAdjustBalance()
{
MyMoneyAccount a;
MyMoneySplit s;
s.setShares(MyMoneyMoney(3, 1));
a.adjustBalance(s);
QVERIFY(a.balance() == MyMoneyMoney(3, 1));
s.setShares(MyMoneyMoney(5, 1));
a.adjustBalance(s, true);
QVERIFY(a.balance() == MyMoneyMoney(-2, 1));
s.setShares(MyMoneyMoney(2, 1));
s.setAction(MyMoneySplit::ActionSplitShares);
a.adjustBalance(s);
QVERIFY(a.balance() == MyMoneyMoney(-4, 1));
s.setShares(MyMoneyMoney(4, 1));
s.setAction(QString());
a.adjustBalance(s);
QVERIFY(a.balance().isZero());
}
void MyMoneyAccountTest::testSubAccounts()
{
MyMoneyAccount a;
- a.setAccountType(MyMoneyAccount::Checkings);
+ a.setAccountType(eMyMoney::Account::Checkings);
a.addAccountId("Subaccount1");
QVERIFY(a.accountList().count() == 1);
a.addAccountId("Subaccount1");
QVERIFY(a.accountList().count() == 1);
a.addAccountId("Subaccount2");
QVERIFY(a.accountList().count() == 2);
}
void MyMoneyAccountTest::testEquality()
{
MyMoneyAccount a;
a.setLastModified(QDate::currentDate());
a.setName("Name");
a.setNumber("Number");
a.setDescription("Desc");
a.setInstitutionId("I-ID");
a.setOpeningDate(QDate::currentDate());
a.setLastReconciliationDate(QDate::currentDate());
- a.setAccountType(MyMoneyAccount::Asset);
+ a.setAccountType(eMyMoney::Account::Asset);
a.setParentAccountId("P-ID");
a.setId("A-ID");
a.setCurrencyId("C-ID");
a.setValue("Key", "Value");
MyMoneyAccount b;
b = a;
QVERIFY(b == a);
a.setName("Noname");
QVERIFY(!(b == a));
b = a;
a.setLastModified(QDate::currentDate().addDays(-1));
QVERIFY(!(b == a));
b = a;
a.setNumber("Nonumber");
QVERIFY(!(b == a));
b = a;
a.setDescription("NoDesc");
QVERIFY(!(b == a));
b = a;
a.setInstitutionId("I-noID");
QVERIFY(!(b == a));
b = a;
a.setOpeningDate(QDate::currentDate().addDays(-1));
QVERIFY(!(b == a));
b = a;
a.setLastReconciliationDate(QDate::currentDate().addDays(-1));
QVERIFY(!(b == a));
b = a;
- a.setAccountType(MyMoneyAccount::Liability);
+ a.setAccountType(eMyMoney::Account::Liability);
QVERIFY(!(b == a));
b = a;
a.setParentAccountId("P-noID");
QVERIFY(!(b == a));
b = a;
a.setId("A-noID");
QVERIFY(!(b == a));
b = a;
a.setCurrencyId("C-noID");
QVERIFY(!(b == a));
b = a;
a.setValue("Key", "noValue");
QVERIFY(!(b == a));
b = a;
a.setValue("noKey", "Value");
QVERIFY(!(b == a));
b = a;
}
void MyMoneyAccountTest::testWriteXML()
{
QString id = "A000001";
QString institutionid = "B000001";
QString parent = "Parent";
MyMoneyAccount r;
- r.setAccountType(MyMoneyAccount::Asset);
+ r.setAccountType(eMyMoney::Account::Asset);
r.setOpeningDate(QDate::currentDate());
r.setLastModified(QDate::currentDate());
r.setDescription("Desc");
r.setName("AccountName");
r.setNumber("465500");
r.setParentAccountId(parent);
r.setInstitutionId(institutionid);
r.setValue(QString("key"), "value");
r.addAccountId("A000002");
r.addReconciliation(QDate(2011, 1, 1), MyMoneyMoney(123, 100));
r.addReconciliation(QDate(2011, 2, 1), MyMoneyMoney(456, 100));
QCOMPARE(r.m_kvp.count(), 2);
QCOMPARE(r.value("key"), QLatin1String("value"));
QCOMPARE(r.value("reconciliationHistory"), QLatin1String("2011-01-01:123/100;2011-02-01:114/25"));
MyMoneyAccount a(id, r);
QDomDocument doc("TEST");
QDomElement el = doc.createElement("ACCOUNT-CONTAINER");
doc.appendChild(el);
a.writeXML(doc, el);
QCOMPARE(doc.doctype().name(), QLatin1String("TEST"));
QDomElement accountContainer = doc.documentElement();
QVERIFY(accountContainer.isElement());
QCOMPARE(accountContainer.tagName(), QLatin1String("ACCOUNT-CONTAINER"));
QVERIFY(accountContainer.childNodes().size() == 1);
QVERIFY(accountContainer.childNodes().at(0).isElement());
QDomElement account = accountContainer.childNodes().at(0).toElement();
QCOMPARE(account.tagName(), QLatin1String("ACCOUNT"));
QCOMPARE(account.attribute("id"), QLatin1String("A000001"));
QCOMPARE(account.attribute("lastreconciled"), QString());
QCOMPARE(account.attribute("institution"), QLatin1String("B000001"));
QCOMPARE(account.attribute("name"), QLatin1String("AccountName"));
QCOMPARE(account.attribute("number"), QLatin1String("465500"));
QCOMPARE(account.attribute("description"), QLatin1String("Desc"));
QCOMPARE(account.attribute("parentaccount"), QLatin1String("Parent"));
QCOMPARE(account.attribute("opened"), QDate::currentDate().toString(Qt::ISODate));
QCOMPARE(account.attribute("type"), QLatin1String("9"));
QCOMPARE(account.attribute("lastmodified"), QDate::currentDate().toString(Qt::ISODate));
QCOMPARE(account.attribute("id"), QLatin1String("A000001"));
QCOMPARE(account.childNodes().size(), 2);
QVERIFY(account.childNodes().at(0).isElement());
QDomElement subAccounts = account.childNodes().at(0).toElement();
QCOMPARE(subAccounts.tagName(), QLatin1String("SUBACCOUNTS"));
QCOMPARE(subAccounts.childNodes().size(), 1);
QVERIFY(subAccounts.childNodes().at(0).isElement());
QDomElement subAccount = subAccounts.childNodes().at(0).toElement();
QCOMPARE(subAccount.tagName(), QLatin1String("SUBACCOUNT"));
QCOMPARE(subAccount.attribute("id"), QLatin1String("A000002"));
QCOMPARE(subAccount.childNodes().size(), 0);
QDomElement keyValuePairs = account.childNodes().at(1).toElement();
QCOMPARE(keyValuePairs.tagName(), QLatin1String("KEYVALUEPAIRS"));
QCOMPARE(keyValuePairs.childNodes().size(), 2);
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);
QVERIFY(keyValuePairs.childNodes().at(1).isElement());
QDomElement keyValuePair2 = keyValuePairs.childNodes().at(1).toElement();
QCOMPARE(keyValuePair2.tagName(), QLatin1String("PAIR"));
QCOMPARE(keyValuePair2.attribute("key"), QLatin1String("reconciliationHistory"));
QCOMPARE(keyValuePair2.attribute("value"), QLatin1String("2011-01-01:123/100;2011-02-01:114/25"));
QCOMPARE(keyValuePair2.childNodes().size(), 0);
}
void MyMoneyAccountTest::testReadXML()
{
MyMoneyAccount a;
QString ref_ok = QString(
"<!DOCTYPE TEST>\n"
"<ACCOUNT-CONTAINER>\n"
" <ACCOUNT parentaccount=\"Parent\" lastmodified=\"%1\" lastreconciled=\"\" institution=\"B000001\" number=\"465500\" opened=\"%2\" type=\"9\" id=\"A000001\" name=\"AccountName\" description=\"Desc\" >\n"
" <SUBACCOUNTS>\n"
" <SUBACCOUNT id=\"A000002\" />\n"
" <SUBACCOUNT id=\"A000003\" />\n"
" </SUBACCOUNTS>\n"
" <KEYVALUEPAIRS>\n"
" <PAIR key=\"key\" value=\"value\" />\n"
" <PAIR key=\"Key\" value=\"Value\" />\n"
" <PAIR key=\"reconciliationHistory\" value=\"2011-01-01:123/100;2011-02-01:114/25\"/>\n"
" </KEYVALUEPAIRS>\n"
" </ACCOUNT>\n"
"</ACCOUNT-CONTAINER>\n").
arg(QDate::currentDate().toString(Qt::ISODate)).arg(QDate::currentDate().toString(Qt::ISODate));
QString ref_false = QString(
"<!DOCTYPE TEST>\n"
"<ACCOUNT-CONTAINER>\n"
" <KACCOUNT parentaccount=\"Parent\" lastmodified=\"%1\" lastreconciled=\"\" institution=\"B000001\" number=\"465500\" opened=\"%2\" type=\"9\" id=\"A000001\" name=\"AccountName\" description=\"Desc\" >\n"
" <SUBACCOUNTS>\n"
" <SUBACCOUNT id=\"A000002\" />\n"
" <SUBACCOUNT id=\"A000003\" />\n"
" </SUBACCOUNTS>\n"
" <KEYVALUEPAIRS>\n"
" <PAIR key=\"key\" value=\"value\" />\n"
" <PAIR key=\"Key\" value=\"Value\" />\n"
" </KEYVALUEPAIRS>\n"
" </KACCOUNT>\n"
"</ACCOUNT-CONTAINER>\n").
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 {
a = MyMoneyAccount(node);
QFAIL("Missing expected exception");
} catch (const MyMoneyException &) {
}
doc.setContent(ref_ok);
node = doc.documentElement().firstChild().toElement();
a.addAccountId("TEST");
a.setValue("KEY", "VALUE");
try {
a = MyMoneyAccount(node);
QVERIFY(a.id() == "A000001");
QVERIFY(a.m_name == "AccountName");
QVERIFY(a.m_parentAccount == "Parent");
QVERIFY(a.m_lastModified == QDate::currentDate());
QVERIFY(a.m_lastReconciliationDate == QDate());
QVERIFY(a.m_institution == "B000001");
QVERIFY(a.m_number == "465500");
QVERIFY(a.m_openingDate == QDate::currentDate());
- QVERIFY(a.m_accountType == MyMoneyAccount::Asset);
+ QVERIFY(a.m_accountType == eMyMoney::Account::Asset);
QVERIFY(a.m_description == "Desc");
QVERIFY(a.accountList().count() == 2);
QVERIFY(a.accountList()[0] == "A000002");
QVERIFY(a.accountList()[1] == "A000003");
QVERIFY(a.pairs().count() == 4);
QVERIFY(a.value("key") == "value");
QVERIFY(a.value("Key") == "Value");
QVERIFY(a.value("lastStatementDate").isEmpty());
QVERIFY(a.reconciliationHistory().count() == 2);
QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 1)] == MyMoneyMoney(123, 100));
QVERIFY(a.reconciliationHistory()[QDate(2011, 2, 1)] == MyMoneyMoney(456, 100));
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
}
void MyMoneyAccountTest::testHasReferenceTo()
{
MyMoneyAccount a;
a.setInstitutionId("I0001");
a.addAccountId("A_001");
a.addAccountId("A_002");
a.setParentAccountId("A_Parent");
a.setCurrencyId("Currency");
QVERIFY(a.hasReferenceTo("I0001") == true);
QVERIFY(a.hasReferenceTo("I0002") == false);
QVERIFY(a.hasReferenceTo("A_001") == false);
QVERIFY(a.hasReferenceTo("A_Parent") == true);
QVERIFY(a.hasReferenceTo("Currency") == true);
}
void MyMoneyAccountTest::testSetClosed()
{
MyMoneyAccount a;
QVERIFY(a.isClosed() == false);
a.setClosed(true);
QVERIFY(a.isClosed() == true);
a.setClosed(false);
QVERIFY(a.isClosed() == false);
}
void MyMoneyAccountTest::specialAccountTypes_data()
{
- QTest::addColumn<MyMoneyAccount::accountTypeE>("accountType");
+ QTest::addColumn<eMyMoney::Account>("accountType");
QTest::addColumn<bool>("incomeExpense");
QTest::addColumn<bool>("assetLibility");
QTest::addColumn<bool>("loan");
// positive and null is debit
- QTest::newRow("unknown") << MyMoneyAccount::UnknownAccountType << false << false << false;
- QTest::newRow("checking") << MyMoneyAccount::Checkings << false << true << false;
- QTest::newRow("savings") << MyMoneyAccount::Savings << false << true << false;
- QTest::newRow("cash") << MyMoneyAccount::Cash << false << true << false;
- QTest::newRow("investment") << MyMoneyAccount::Investment << false << true << false;
- QTest::newRow("asset") << MyMoneyAccount::Asset << false << true << false;
- QTest::newRow("currency") << MyMoneyAccount::Currency << false << true << false;
- QTest::newRow("expense") << MyMoneyAccount::Expense << true << false << false;
- QTest::newRow("moneymarket") << MyMoneyAccount::MoneyMarket << false << true << false;
- QTest::newRow("certificatedeposit") << MyMoneyAccount::CertificateDep << false << true << false;
- QTest::newRow("assetloan") << MyMoneyAccount::AssetLoan << false << true << true;
- QTest::newRow("stock") << MyMoneyAccount::Stock << false << true << false;
- QTest::newRow("creditcard") << MyMoneyAccount::CreditCard << false << true << false;
- QTest::newRow("loan") << MyMoneyAccount::Loan << false << true << true;
- QTest::newRow("liability") << MyMoneyAccount::Liability << false << true << false;
- QTest::newRow("income") << MyMoneyAccount::Income << true << false << false;
- QTest::newRow("equity") << MyMoneyAccount::Equity << false << false << false;
+ QTest::newRow("unknown") << eMyMoney::Account::Unknown << false << false << false;
+ QTest::newRow("checking") << eMyMoney::Account::Checkings << false << true << false;
+ QTest::newRow("savings") << eMyMoney::Account::Savings << false << true << false;
+ QTest::newRow("cash") << eMyMoney::Account::Cash << false << true << false;
+ QTest::newRow("investment") << eMyMoney::Account::Investment << false << true << false;
+ QTest::newRow("asset") << eMyMoney::Account::Asset << false << true << false;
+ QTest::newRow("currency") << eMyMoney::Account::Currency << false << true << false;
+ QTest::newRow("expense") << eMyMoney::Account::Expense << true << false << false;
+ QTest::newRow("moneymarket") << eMyMoney::Account::MoneyMarket << false << true << false;
+ QTest::newRow("certificatedeposit") << eMyMoney::Account::CertificateDep << false << true << false;
+ QTest::newRow("assetloan") << eMyMoney::Account::AssetLoan << false << true << true;
+ QTest::newRow("stock") << eMyMoney::Account::Stock << false << true << false;
+ QTest::newRow("creditcard") << eMyMoney::Account::CreditCard << false << true << false;
+ QTest::newRow("loan") << eMyMoney::Account::Loan << false << true << true;
+ QTest::newRow("liability") << eMyMoney::Account::Liability << false << true << false;
+ QTest::newRow("income") << eMyMoney::Account::Income << true << false << false;
+ QTest::newRow("equity") << eMyMoney::Account::Equity << false << false << false;
}
void MyMoneyAccountTest::specialAccountTypes()
{
- QFETCH(MyMoneyAccount::accountTypeE, accountType);
+ QFETCH(eMyMoney::Account, accountType);
QFETCH(bool, incomeExpense);
QFETCH(bool, assetLibility);
QFETCH(bool, loan);
MyMoneyAccount a;
a.setAccountType(accountType);
QCOMPARE(a.isIncomeExpense(), incomeExpense);
QCOMPARE(a.isAssetLiability(), assetLibility);
QCOMPARE(a.isLoan(), loan);
}
void MyMoneyAccountTest::addReconciliation()
{
MyMoneyAccount a;
QVERIFY(a.addReconciliation(QDate(2011, 1, 2), MyMoneyMoney(123, 100)) == true);
QVERIFY(a.reconciliationHistory().count() == 1);
QVERIFY(a.addReconciliation(QDate(2011, 2, 1), MyMoneyMoney(456, 100)) == true);
QVERIFY(a.reconciliationHistory().count() == 2);
QVERIFY(a.addReconciliation(QDate(2011, 2, 1), MyMoneyMoney(789, 100)) == true);
QVERIFY(a.reconciliationHistory().count() == 2);
QVERIFY(a.reconciliationHistory().values().last() == MyMoneyMoney(789, 100));
}
void MyMoneyAccountTest::reconciliationHistory()
{
MyMoneyAccount a;
QVERIFY(a.reconciliationHistory().isEmpty() == true);
QVERIFY(a.addReconciliation(QDate(2011, 1, 2), MyMoneyMoney(123, 100)) == true);
QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 2)] == MyMoneyMoney(123, 100));
QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 1)] == MyMoneyMoney());
QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 3)] == MyMoneyMoney());
QVERIFY(a.addReconciliation(QDate(2011, 2, 1), MyMoneyMoney(456, 100)) == true);
QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 2)] == MyMoneyMoney(123, 100));
QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 1)] == MyMoneyMoney());
QVERIFY(a.reconciliationHistory()[QDate(2011, 1, 3)] == MyMoneyMoney());
QVERIFY(a.reconciliationHistory()[QDate(2011, 2, 1)] == MyMoneyMoney(456, 100));
QVERIFY(a.reconciliationHistory().count() == 2);
}
void MyMoneyAccountTest::testElementNames()
{
QMetaEnum e = QMetaEnum::fromType<MyMoneyAccount::elNameE>();
for (int i = 0; i < e.keyCount(); ++i) {
bool isEmpty = MyMoneyAccount::getElName(static_cast<MyMoneyAccount::elNameE>(e.value(i))).isEmpty();
if (isEmpty)
qWarning() << "Empty element's name" << e.key(i);
QVERIFY(!isEmpty);
}
}
void MyMoneyAccountTest::testAttributeNames()
{
QMetaEnum e = QMetaEnum::fromType<MyMoneyAccount::attrNameE>();
for (int i = 0; i < e.keyCount(); ++i) {
bool isEmpty = MyMoneyAccount::getAttrName(static_cast<MyMoneyAccount::attrNameE>(e.value(i))).isEmpty();
if (isEmpty)
qWarning() << "Empty attribute's name" << e.key(i);
QVERIFY(!isEmpty);
}
}
diff --git a/kmymoney/mymoney/tests/mymoneyfile-test.cpp b/kmymoney/mymoney/tests/mymoneyfile-test.cpp
index 300fba973..86dc8121c 100644
--- a/kmymoney/mymoney/tests/mymoneyfile-test.cpp
+++ b/kmymoney/mymoney/tests/mymoneyfile-test.cpp
@@ -1,2751 +1,2751 @@
/***************************************************************************
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 <iostream>
#include <memory>
#include <unistd.h>
#include <QFile>
#include <QDataStream>
#include <QList>
#include <QtTest/QtTest>
#include "mymoneytestutils.h"
#include "payeeidentifier/ibanandbic/ibanbic.h"
#include "payeeidentifier/payeeidentifierloader.h"
#include "payeeidentifiertyped.h"
QTEST_GUILESS_MAIN(MyMoneyFileTest)
-void MyMoneyFileTest::objectAdded(MyMoneyFile::notificationObjectT type, const MyMoneyObject * const obj)
+void MyMoneyFileTest::objectAdded(eMyMoney::File::Object type, const MyMoneyObject * const obj)
{
Q_UNUSED(type);
m_objectsAdded += obj->id();
}
-void MyMoneyFileTest::objectRemoved(MyMoneyFile::notificationObjectT type, const QString& id)
+void MyMoneyFileTest::objectRemoved(eMyMoney::File::Object type, const QString& id)
{
Q_UNUSED(type);
m_objectsRemoved += id;
}
-void MyMoneyFileTest::objectModified(MyMoneyFile::notificationObjectT type, const MyMoneyObject * const obj)
+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(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), this, SLOT(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)));
- connect(m, SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), this, SLOT(objectRemoved(MyMoneyFile::notificationObjectT,QString)));
- connect(m, SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), this, SLOT(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)));
+ 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;
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<unsigned>(0));
QCOMPARE(m->dirty(), false);
QCOMPARE(m->accountCount(), static_cast<unsigned>(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<unsigned>(0));
storage->m_dirty = false;
clearObjectLists();
MyMoneyFileTransaction ft;
try {
m->addInstitution(institution);
ft.commit();
QCOMPARE(institution.id(), QLatin1String("I000001"));
QCOMPARE(m->institutionCount(), static_cast<unsigned>(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<unsigned>(1));
}
ft.restart();
try {
m->addInstitution(institution_noname);
QFAIL("Missing expected exception");
} catch (const MyMoneyException &) {
ft.commit();
QCOMPARE(m->institutionCount(), static_cast<unsigned>(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->m_dirty = false;
MyMoneyFileTransaction ft;
try {
m->addInstitution(institution);
ft.commit();
QCOMPARE(institution.id(), QLatin1String("I000002"));
QCOMPARE(m->institutionCount(), static_cast<unsigned>(2));
QCOMPARE(m->dirty(), true);
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
storage->m_dirty = false;
try {
institution = m->institution("I000001");
QCOMPARE(institution.id(), QLatin1String("I000001"));
QCOMPARE(m->institutionCount(), static_cast<unsigned>(2));
QCOMPARE(m->dirty(), false);
institution = m->institution("I000002");
QCOMPARE(institution.id(), QLatin1String("I000002"));
QCOMPARE(m->institutionCount(), static_cast<unsigned>(2));
QCOMPARE(m->dirty(), false);
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
}
void MyMoneyFileTest::testRemoveInstitution()
{
testAddTwoInstitutions();
MyMoneyInstitution i;
QCOMPARE(m->institutionCount(), static_cast<unsigned>(2));
i = m->institution("I000001");
QCOMPARE(i.id(), QLatin1String("I000001"));
QCOMPARE(i.accountCount(), static_cast<unsigned>(0));
clearObjectLists();
storage->m_dirty = false;
MyMoneyFileTransaction ft;
try {
m->removeInstitution(i);
QCOMPARE(m_objectsRemoved.count(), 0);
ft.commit();
QCOMPARE(m->institutionCount(), static_cast<unsigned>(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->m_dirty = false;
try {
m->institution("I000001");
QFAIL("Missing expected exception");
} catch (const MyMoneyException &) {
QCOMPARE(m->institutionCount(), static_cast<unsigned>(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<unsigned>(1));
QCOMPARE(m->dirty(), false);
QCOMPARE(m_objectsRemoved.count(), 0);
}
}
void MyMoneyFileTest::testInstitutionRetrieval()
{
testAddOneInstitution();
storage->m_dirty = false;
MyMoneyInstitution institution;
QCOMPARE(m->institutionCount(), static_cast<unsigned>(1));
try {
institution = m->institution("I000001");
QCOMPARE(institution.id(), QLatin1String("I000001"));
QCOMPARE(m->institutionCount(), static_cast<unsigned>(1));
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
try {
institution = m->institution("I000002");
QFAIL("Missing expected exception");
} catch (const MyMoneyException &) {
QCOMPARE(m->institutionCount(), static_cast<unsigned>(1));
}
QCOMPARE(m->dirty(), false);
}
void MyMoneyFileTest::testInstitutionListRetrieval()
{
QList<MyMoneyInstitution> list;
storage->m_dirty = false;
list = m->institutionList();
QCOMPARE(m->dirty(), false);
QCOMPARE(list.count(), 0);
testAddTwoInstitutions();
storage->m_dirty = false;
list = m->institutionList();
QCOMPARE(m->dirty(), false);
QCOMPARE(list.count(), 2);
QList<MyMoneyInstitution>::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->m_dirty = false;
clearObjectLists();
MyMoneyFileTransaction ft;
try {
m->modifyInstitution(institution);
ft.commit();
QCOMPARE(institution.id(), QLatin1String("I000001"));
QCOMPARE(m->institutionCount(), static_cast<unsigned>(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->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<unsigned>(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->m_dirty = false;
user.setName("Name");
m->setUser(user);
QCOMPARE(m->dirty(), true);
storage->m_dirty = false;
user.setAddress("Street");
m->setUser(user);
QCOMPARE(m->dirty(), true);
storage->m_dirty = false;
user.setCity("Town");
m->setUser(user);
QCOMPARE(m->dirty(), true);
storage->m_dirty = false;
user.setState("County");
m->setUser(user);
QCOMPARE(m->dirty(), true);
storage->m_dirty = false;
user.setPostcode("Postcode");
m->setUser(user);
QCOMPARE(m->dirty(), true);
storage->m_dirty = false;
user.setTelephone("Telephone");
m->setUser(user);
QCOMPARE(m->dirty(), true);
storage->m_dirty = false;
user.setEmail("Email");
m->setUser(user);
QCOMPARE(m->dirty(), true);
storage->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(MyMoneyAccount::Checkings);
- b.setAccountType(MyMoneyAccount::Checkings);
+ a.setAccountType(eMyMoney::Account::Checkings);
+ b.setAccountType(eMyMoney::Account::Checkings);
MyMoneyInstitution institution;
storage->m_dirty = false;
QCOMPARE(m->accountCount(), static_cast<unsigned>(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<unsigned>(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<unsigned>(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->m_dirty = false;
// check if we can get the same info to a different object
c = m->account("A000001");
- QCOMPARE(c.accountType(), MyMoneyAccount::Checkings);
+ QCOMPARE(c.accountType(), eMyMoney::Account::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<unsigned>(7));
institution = m->institution("I000001");
QCOMPARE(institution.accountCount(), static_cast<unsigned>(1));
QCOMPARE(institution.accountList()[0], QLatin1String("A000001"));
institution = m->institution("I000002");
QCOMPARE(institution.accountCount(), static_cast<unsigned>(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(), MyMoneyAccount::Checkings);
+ QCOMPARE(p.accountType(), eMyMoney::Account::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(MyMoneyAccount::Income);
+ a.setAccountType(eMyMoney::Account::Income);
a.setOpeningDate(QDate::currentDate());
- b.setAccountType(MyMoneyAccount::Expense);
+ b.setAccountType(eMyMoney::Account::Expense);
storage->m_dirty = false;
QCOMPARE(m->accountCount(), static_cast<unsigned>(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<unsigned>(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<unsigned>(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->m_dirty = false;
MyMoneyAccount p = m->account("A000001");
MyMoneyInstitution institution;
- QCOMPARE(p.accountType(), MyMoneyAccount::Checkings);
+ QCOMPARE(p.accountType(), eMyMoney::Account::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<unsigned>(7));
- QCOMPARE(p.accountType(), MyMoneyAccount::Checkings);
+ QCOMPARE(p.accountType(), eMyMoney::Account::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->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<unsigned>(7));
- QCOMPARE(p.accountType(), MyMoneyAccount::Checkings);
+ QCOMPARE(p.accountType(), eMyMoney::Account::Checkings);
QCOMPARE(p.name(), QLatin1String("New account name"));
QCOMPARE(p.institutionId(), QLatin1String("I000002"));
institution = m->institution("I000001");
QCOMPARE(institution.accountCount(), static_cast<unsigned>(0));
institution = m->institution("I000002");
QCOMPARE(institution.accountCount(), static_cast<unsigned>(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->m_dirty = false;
// try to change to an account type that is allowed
- p.setAccountType(MyMoneyAccount::Savings);
+ p.setAccountType(eMyMoney::Account::Savings);
ft.restart();
try {
m->modifyAccount(p);
ft.commit();
QCOMPARE(m->dirty(), true);
QCOMPARE(m->accountCount(), static_cast<unsigned>(7));
- QCOMPARE(p.accountType(), MyMoneyAccount::Savings);
+ QCOMPARE(p.accountType(), eMyMoney::Account::Savings);
QCOMPARE(p.name(), QLatin1String("New account name"));
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception!");
}
storage->m_dirty = false;
// try to change to an account type that is not allowed
- p.setAccountType(MyMoneyAccount::CreditCard);
+ p.setAccountType(eMyMoney::Account::CreditCard);
ft.restart();
try {
m->modifyAccount(p);
QFAIL("Expecting exception!");
} catch (const MyMoneyException &) {
ft.commit();
}
storage->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->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<unsigned>(7));
storage->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<unsigned>(6));
institution = m->institution("I000001");
QCOMPARE(institution.accountCount(), static_cast<unsigned>(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<unsigned>(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<unsigned>(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<MyMoneyAccount> list;
storage->m_dirty = false;
m->accountList(list);
QCOMPARE(m->dirty(), false);
QCOMPARE(list.count(), 0);
testAddAccounts();
storage->m_dirty = false;
list.clear();
m->accountList(list);
QCOMPARE(m->dirty(), false);
QCOMPARE(list.count(), 2);
- QCOMPARE(list[0].accountType(), MyMoneyAccount::Checkings);
- QCOMPARE(list[1].accountType(), MyMoneyAccount::Checkings);
+ QCOMPARE(list[0].accountType(), eMyMoney::Account::Checkings);
+ QCOMPARE(list[1].accountType(), eMyMoney::Account::Checkings);
}
void MyMoneyFileTest::testAddTransaction()
{
testAddAccounts();
MyMoneyTransaction t, p;
MyMoneyAccount exp1;
- exp1.setAccountType(MyMoneyAccount::Expense);
+ exp1.setAccountType(eMyMoney::Account::Expense);
exp1.setName("Expense1");
MyMoneyAccount exp2;
- exp2.setAccountType(MyMoneyAccount::Expense);
+ exp2.setAccountType(eMyMoney::Account::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<unsigned>(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->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->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<unsigned>(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<unsigned>(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<unsigned>(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->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->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->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->m_dirty = false;
MyMoneyFileTransaction ft;
clearObjectLists();
try {
m->removeTransaction(t);
ft.commit();
QCOMPARE(m->dirty(), true);
QCOMPARE(m->transactionCount(), static_cast<unsigned>(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(STD_ACC_LIABILITY, "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(STD_ACC_ASSET, 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(STD_ACC_EXPENSE, "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(STD_ACC_INCOME, "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<payeeIdentifiers::ibanBic> 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<payeeIdentifiers::ibanBic>(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(STD_ACC_EXPENSE);
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->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;
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();
m->detachStorage(p);
QCOMPARE(m->storageAttached(), false);
QCOMPARE(m->storage(), static_cast<IMyMoneyStorage*>(0));
m->attachStorage(p);
QCOMPARE(m->storageAttached(), true);
QVERIFY(m->storage() != 0);
}
void MyMoneyFileTest::testHasAccount()
{
testAddAccounts();
MyMoneyAccount a, b;
- a.setAccountType(MyMoneyAccount::Checkings);
+ a.setAccountType(eMyMoney::Account::Checkings);
a.setName("Account3");
b = m->account("A000001");
MyMoneyFileTransaction ft;
try {
m->addAccount(a, b);
ft.commit();
QCOMPARE(m->accountCount(), static_cast<unsigned>(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(MyMoneyAccount::Investment);
+ i.setAccountType(eMyMoney::Account::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<MyMoneyAccount::accountTypeE> list;
- list << MyMoneyAccount::Checkings;
- list << MyMoneyAccount::Savings;
- list << MyMoneyAccount::Cash;
- list << MyMoneyAccount::CreditCard;
- list << MyMoneyAccount::Loan;
- list << MyMoneyAccount::CertificateDep;
- list << MyMoneyAccount::Investment;
- list << MyMoneyAccount::MoneyMarket;
- list << MyMoneyAccount::Asset;
- list << MyMoneyAccount::Liability;
- list << MyMoneyAccount::Currency;
- list << MyMoneyAccount::Income;
- list << MyMoneyAccount::Expense;
- list << MyMoneyAccount::AssetLoan;
-
- QList<MyMoneyAccount::accountTypeE>::Iterator it;
+ QList<eMyMoney::Account> list;
+ list << eMyMoney::Account::Checkings;
+ list << eMyMoney::Account::Savings;
+ list << eMyMoney::Account::Cash;
+ list << eMyMoney::Account::CreditCard;
+ list << eMyMoney::Account::Loan;
+ list << eMyMoney::Account::CertificateDep;
+ list << eMyMoney::Account::Investment;
+ list << eMyMoney::Account::MoneyMarket;
+ list << eMyMoney::Account::Asset;
+ list << eMyMoney::Account::Liability;
+ list << eMyMoney::Account::Currency;
+ list << eMyMoney::Account::Income;
+ list << eMyMoney::Account::Expense;
+ list << eMyMoney::Account::AssetLoan;
+
+ QList<eMyMoney::Account>::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", *it);
+ 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(MyMoneyAccount::Stock);
+ a.setAccountType(eMyMoney::Account::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<MyMoneyAccount::accountTypeE> list;
- list << MyMoneyAccount::Checkings;
- list << MyMoneyAccount::Savings;
- list << MyMoneyAccount::Cash;
- list << MyMoneyAccount::CertificateDep;
- list << MyMoneyAccount::MoneyMarket;
- list << MyMoneyAccount::Asset;
- list << MyMoneyAccount::AssetLoan;
- list << MyMoneyAccount::Currency;
+ QList<eMyMoney::Account> list;
+ list << eMyMoney::Account::Checkings;
+ list << eMyMoney::Account::Savings;
+ list << eMyMoney::Account::Cash;
+ list << eMyMoney::Account::CertificateDep;
+ list << eMyMoney::Account::MoneyMarket;
+ list << eMyMoney::Account::Asset;
+ list << eMyMoney::Account::AssetLoan;
+ list << eMyMoney::Account::Currency;
parent = m->asset();
testReparentEquity(list, parent);
list.clear();
- list << MyMoneyAccount::CreditCard;
- list << MyMoneyAccount::Loan;
- list << MyMoneyAccount::Liability;
+ list << eMyMoney::Account::CreditCard;
+ list << eMyMoney::Account::Loan;
+ list << eMyMoney::Account::Liability;
parent = m->liability();
testReparentEquity(list, parent);
list.clear();
- list << MyMoneyAccount::Income;
+ list << eMyMoney::Account::Income;
parent = m->income();
testReparentEquity(list, parent);
list.clear();
- list << MyMoneyAccount::Expense;
+ list << eMyMoney::Account::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<MyMoneyAccount::accountTypeE>& list, MyMoneyAccount& parent)
+void MyMoneyFileTest::testReparentEquity(QList<eMyMoney::Account>& list, MyMoneyAccount& parent)
{
MyMoneyAccount a;
MyMoneyAccount stock = m->account("A000002");
- QList<MyMoneyAccount::accountTypeE>::Iterator it;
+ QList<eMyMoney::Account>::Iterator it;
MyMoneyFileTransaction ft;
for (it = list.begin(); it != list.end(); ++it) {
- a.setName(QString("Testaccount %1").arg(*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", *it);
+ 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;
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(MyMoneyAccount::Checkings);
+ a.setAccountType(eMyMoney::Account::Checkings);
MyMoneyInstitution institution;
storage->m_dirty = false;
QCOMPARE(m->accountCount(), static_cast<unsigned>(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(MyMoneyAccount::Checkings);
+ a.setAccountType(eMyMoney::Account::Checkings);
storage->m_dirty = false;
QCOMPARE(m->accountCount(), static_cast<unsigned>(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<unsigned>(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, MyMoneyTransactionFilter::notReconciled), 0);
+ 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, MyMoneyTransactionFilter::notReconciled), 1);
+ 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(MyMoneySplit::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, MyMoneyTransactionFilter::notReconciled), 0);
+ 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(MyMoneySplit::Cleared);
split2.setAccountId("A000004");
split2.setValue(MyMoneyMoney(1000, 100));
split2.setShares(MyMoneyMoney(1000, 100));
split2.setReconcileFlag(MyMoneySplit::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(MyMoneySplit::Cleared);
split4.setAccountId("A000004");
split4.setValue(MyMoneyMoney(2000, 100));
split4.setShares(MyMoneyMoney(2000, 100));
split4.setReconcileFlag(MyMoneySplit::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(MyMoneyAccount::Expense);
+ exp1.setAccountType(eMyMoney::Account::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(MyMoneyAccount::Stock);
+ stock.setAccountType(eMyMoney::Account::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(MyMoneySplit::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(MyMoneyAccount::Expense);
+ vat.setAccountType(eMyMoney::Account::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());
}
diff --git a/kmymoney/mymoney/tests/mymoneyfile-test.h b/kmymoney/mymoney/tests/mymoneyfile-test.h
index 2ca397b15..0707ec0a8 100644
--- a/kmymoney/mymoney/tests/mymoneyfile-test.h
+++ b/kmymoney/mymoney/tests/mymoneyfile-test.h
@@ -1,128 +1,128 @@
/***************************************************************************
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 <QtCore/QObject>
#include <QtCore/QList>
#define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyFileTest;
#include "mymoneyfile.h"
#include "storage/mymoneyseqaccessmgr.h"
class MyMoneyFileTest : public QObject
{
Q_OBJECT
protected:
MyMoneyFile *m;
MyMoneySeqAccessMgr* storage;
MyMoneyAccount m_inv;
private 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();
private slots:
- void objectAdded(MyMoneyFile::notificationObjectT type, const MyMoneyObject * const obj);
- void objectModified(MyMoneyFile::notificationObjectT type, const MyMoneyObject * const obj);
- void objectRemoved(MyMoneyFile::notificationObjectT type, const QString& id);
+ 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<MyMoneyAccount::accountTypeE>& list, MyMoneyAccount& parent);
+ void testReparentEquity(QList<eMyMoney::Account>& 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 c36485552..4c7a3bcab 100644
--- a/kmymoney/mymoney/tests/mymoneyforecast-test.cpp
+++ b/kmymoney/mymoney/tests/mymoneyforecast-test.cpp
@@ -1,973 +1,976 @@
/***************************************************************************
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 <iostream>
#include <QList>
#include <QtTest/QtTest>
#include "mymoneybudget.h"
#include "mymoneyexception.h"
#include "mymoneystoragedump.h"
#include "mymoneystoragexml.h"
#include "reportstestcommon.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;
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"), MyMoneyAccount::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset, "USD");
- acCredit = makeAccount(QString("Credit Card"), MyMoneyAccount::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability, "USD");
- acSolo = makeAccount(QString("Solo"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense, "USD");
- acParent = makeAccount(QString("Parent"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense, "USD");
- acChild = makeAccount(QString("Child"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent, "USD");
- acForeign = makeAccount(QString("Foreign"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense, "USD");
- acInvestment = makeAccount("Investment", MyMoneyAccount::Investment, moZero, QDate(2004, 1, 1), acAsset, "USD");
-
- acSecondChild = makeAccount(QString("Second Child"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent, "USD");
- acGrandChild1 = makeAccount(QString("Grand Child 1"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild, "USD");
- acGrandChild2 = makeAccount(QString("Grand Child 2"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild, "USD");
+ acChecking = makeAccount(QString("Checking Account"), Account::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset, "USD");
+ acCredit = makeAccount(QString("Credit Card"), Account::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability, "USD");
+ acSolo = makeAccount(QString("Solo"), Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense, "USD");
+ acParent = makeAccount(QString("Parent"), Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense, "USD");
+ acChild = makeAccount(QString("Child"), Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent, "USD");
+ acForeign = makeAccount(QString("Foreign"), Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense, "USD");
+ acInvestment = makeAccount("Investment", Account::Investment, moZero, QDate(2004, 1, 1), acAsset, "USD");
+
+ acSecondChild = makeAccount(QString("Second Child"), Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent, "USD");
+ acGrandChild1 = makeAccount(QString("Grand Child 1"), Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild, "USD");
+ acGrandChild2 = makeAccount(QString("Grand Child 2"), Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild, "USD");
//this account added to have an account to test opening date calculations
- acCash = makeAccount(QString("Cash"), MyMoneyAccount::Cash, moCreditOpen, QDate::currentDate().addDays(-2), acAsset, "USD");
+ acCash = makeAccount(QString("Cash"), Account::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::ActionWithdrawal, this->moT1, acChecking, acSolo);
TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::ActionDeposit, -(this->moT2), acCredit, acParent);
TransactionHelper t3(QDate::currentDate().addDays(-1), MyMoneySplit::ActionTransfer, 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::ActionDeposit, -moT2, acCredit, acParent);
TransactionHelper t5(QDate::currentDate().addDays(-10), MyMoneySplit::ActionDeposit, -moT2, acCredit, acParent);
TransactionHelper t6(QDate::currentDate().addDays(-3), MyMoneySplit::ActionDeposit, -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<MyMoneyAccount> 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::ActionDeposit, -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::ActionDeposit, moT2, acCash, acParent);
TransactionHelper t3(QDate::currentDate().addDays(-1), MyMoneySplit::ActionDeposit, 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::ActionWithdrawal, this->moT1, acChecking, acSolo);
TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::ActionDeposit, -(this->moT2), acCredit, acParent);
TransactionHelper t3(QDate::currentDate().addDays(-1), MyMoneySplit::ActionTransfer, 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::ActionDeposit, -moT1, acCash, acParent);
TransactionHelper t2(QDate::currentDate().addDays(2), MyMoneySplit::ActionDeposit, -moT2, acCash, acParent);
TransactionHelper t3(QDate::currentDate().addDays(3), MyMoneySplit::ActionDeposit, -moT3, acCash, acParent);
TransactionHelper t4(QDate::currentDate().addDays(10), MyMoneySplit::ActionDeposit, -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",
- MyMoneySchedule::TYPE_BILL,
- MyMoneySchedule::OCCUR_WEEKLY, 1,
- MyMoneySchedule::STYPE_DIRECTDEBIT,
+ 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(MyMoneySplit::Reconciled);
t.addSplit(s);
s.setPayeeId("P000001");
s.setShares(-moT2);
s.setValue(-moT2);
s.setAccountId(a_cash.id());
s.setBankID("SPID2");
s.setReconcileFlag(MyMoneySplit::Cleared);
s.clearId();
t.addSplit(s);
sch.setTransaction(t);
file->addSchedule(sch);
ft.commit();
MyMoneyFileTransaction ft3;
MyMoneySchedule sch3("A Name1",
- MyMoneySchedule::TYPE_BILL,
- MyMoneySchedule::OCCUR_WEEKLY, 1,
- MyMoneySchedule::STYPE_DIRECTDEBIT,
+ 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(MyMoneySplit::Reconciled);
t3.addSplit(s3);
s3.setPayeeId("P000001");
s3.setShares(-moT2);
s3.setValue(-moT2);
s3.setAccountId(a_cash.id());
s3.setBankID("SPID2");
s3.setReconcileFlag(MyMoneySplit::Cleared);
s3.clearId();
t3.addSplit(s3);
sch3.setTransaction(t3);
file->addSchedule(sch3);
ft3.commit();
MyMoneyFileTransaction ft2;
MyMoneySchedule sch2("A Name2",
- MyMoneySchedule::TYPE_BILL,
- MyMoneySchedule::OCCUR_WEEKLY, 1,
- MyMoneySchedule::STYPE_DIRECTDEBIT,
+ 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(MyMoneySplit::Reconciled);
t2.addSplit(s2);
s2.setPayeeId("P000001");
s2.setShares(-moT1);
s2.setValue(-moT1);
s2.setAccountId(a_cash.id());
s2.setBankID("SPID2");
s2.setReconcileFlag(MyMoneySplit::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::ActionDeposit, -moT1, acCash, acParent);
TransactionHelper t2(QDate::currentDate().addDays(2), MyMoneySplit::ActionDeposit, moT2, acCash, acParent);
TransactionHelper t3(QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, -moT1, acCredit, acParent);
TransactionHelper t4(QDate::currentDate().addDays(4), MyMoneySplit::ActionWithdrawal, 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::ActionWithdrawal, -moT1, acChecking, acSolo);
TransactionHelper t2(QDate::currentDate().addDays(2), MyMoneySplit::ActionTransfer, (moT5), acCash, acCredit);
TransactionHelper t3(QDate::currentDate().addDays(2), MyMoneySplit::ActionWithdrawal, (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::ActionWithdrawal, this->moT1, acCash, acSolo);
TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, 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::ActionWithdrawal, this->moT1, acCash, acSolo);
TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, 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<QDate> dateList;
dateList = a.accountMinimumBalanceDateList(a_cash);
QList<QDate>::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::ActionWithdrawal, this->moT1, acCash, acSolo);
TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, 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<QDate> dateList;
dateList = a.accountMaximumBalanceDateList(a_cash);
QList<QDate>::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::ActionWithdrawal, this->moT1, acCash, acSolo);
TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::ActionWithdrawal, 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::ActionWithdrawal, this->moT1, acCash, acSolo);
TransactionHelper t2(QDate(2005, 1, 15), MyMoneySplit::ActionWithdrawal, this->moT2, acCash, acParent);
TransactionHelper t3(QDate(2005, 1, 30), MyMoneySplit::ActionWithdrawal, this->moT3, acCash, acSolo);
TransactionHelper t4(QDate(2006, 1, 25), MyMoneySplit::ActionWithdrawal, this->moT4, acCash, acParent);
TransactionHelper t5(QDate(2005, 4, 3), MyMoneySplit::ActionWithdrawal, this->moT1, acCash, acSolo);
TransactionHelper t6(QDate(2006, 5, 15), MyMoneySplit::ActionWithdrawal, this->moT2, acCash, acParent);
TransactionHelper t7(QDate(2005, 8, 3), MyMoneySplit::ActionWithdrawal, this->moT3, acCash, acSolo);
TransactionHelper t8(QDate(2006, 9, 15), MyMoneySplit::ActionWithdrawal, 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",
- MyMoneySchedule::TYPE_BILL,
- MyMoneySchedule::OCCUR_MONTHLY, 1,
- MyMoneySchedule::STYPE_DIRECTDEBIT,
+ 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(MyMoneySplit::Reconciled);
t10.addSplit(s);
s.setPayeeId("P000001");
s.setShares(-moT2);
s.setValue(-moT2);
s.setAccountId(a_cash.id());
s.setBankID("SPID2");
s.setReconcileFlag(MyMoneySplit::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::ActionWithdrawal, this->moT1, acChecking, acSolo);
TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::ActionDeposit, -(this->moT2), acCredit, acParent);
TransactionHelper t3(QDate::currentDate().addDays(-1), MyMoneySplit::ActionTransfer, this->moT1, acCredit, acChecking);
//TODO Add tests specific for linear regression
}
diff --git a/kmymoney/mymoney/tests/mymoneyschedule-test.cpp b/kmymoney/mymoney/tests/mymoneyschedule-test.cpp
index e303bdad4..578c860a6 100644
--- a/kmymoney/mymoney/tests/mymoneyschedule-test.cpp
+++ b/kmymoney/mymoney/tests/mymoneyschedule-test.cpp
@@ -1,1754 +1,1756 @@
/***************************************************************************
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 <QList>
#include <QtTest/QtTest>
#define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyScheduleTest;
#include "mymoneysplit.h"
#include "mymoneymoney.h"
#include "mymoneyschedule.h"
#include "mymoneyfile.h"
#include "storage/mymoneyseqaccessmgr.h"
QTEST_GUILESS_MAIN(MyMoneyScheduleTest)
+using namespace eMyMoney;
+
void MyMoneyScheduleTest::testEmptyConstructor()
{
MyMoneySchedule s;
QCOMPARE(s.id().isEmpty(), true);
- QCOMPARE(s.m_occurrence, MyMoneySchedule::OCCUR_ANY);
- QCOMPARE(s.m_type, MyMoneySchedule::TYPE_ANY);
- QCOMPARE(s.m_paymentType, MyMoneySchedule::STYPE_ANY);
+ QCOMPARE(s.m_occurrence, Schedule::Occurrence::Any);
+ QCOMPARE(s.m_type, Schedule::Type::Any);
+ QCOMPARE(s.m_paymentType, Schedule::PaymentType::Any);
QCOMPARE(s.m_fixed, false);
QCOMPARE(!s.m_startDate.isValid(), true);
QCOMPARE(!s.m_endDate.isValid(), true);
QCOMPARE(!s.m_lastPayment.isValid(), true);
QCOMPARE(s.m_autoEnter, false);
QCOMPARE(s.m_name.isEmpty(), true);
QCOMPARE(s.willEnd(), false);
}
void MyMoneyScheduleTest::testConstructor()
{
MyMoneySchedule s("A Name",
- MyMoneySchedule::TYPE_BILL,
- MyMoneySchedule::OCCUR_WEEKLY, 1,
- MyMoneySchedule::STYPE_DIRECTDEBIT,
+ Schedule::Type::Bill,
+ Schedule::Occurrence::Weekly, 1,
+ Schedule::PaymentType::DirectDebit,
QDate::currentDate(),
QDate(),
true,
true);
- QCOMPARE(s.type(), MyMoneySchedule::TYPE_BILL);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_WEEKLY);
+ QCOMPARE(s.type(), Schedule::Type::Bill);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly);
QCOMPARE(s.occurrenceMultiplier(), 1);
- QCOMPARE(s.paymentType(), MyMoneySchedule::STYPE_DIRECTDEBIT);
+ 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.m_endDate.isValid(), true);
QCOMPARE(!s.m_lastPayment.isValid(), true);
}
void MyMoneyScheduleTest::testSetFunctions()
{
MyMoneySchedule s;
s.setId("SCHED001");
QCOMPARE(s.id(), QLatin1String("SCHED001"));
- s.setType(MyMoneySchedule::TYPE_BILL);
- QCOMPARE(s.type(), MyMoneySchedule::TYPE_BILL);
+ 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.setId("SCHED001");
- s.setType(MyMoneySchedule::TYPE_BILL);
+ s.setType(Schedule::Type::Bill);
MyMoneySchedule s2(s);
QCOMPARE(s.id(), s2.id());
QCOMPARE(s.type(), s2.type());
}
void MyMoneyScheduleTest::testAssignmentConstructor()
{
MyMoneySchedule s;
s.setId("SCHED001");
- s.setType(MyMoneySchedule::TYPE_BILL);
+ 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(
"<!DOCTYPE TEST>\n"
"<SCHEDULE-CONTAINER>\n"
" <SCHEDULED_TX startDate=\"%1\" autoEnter=\"0\" weekendOption=\"2\" lastPayment=\"%2\" paymentType=\"8\" endDate=\"\" type=\"5\" id=\"SCH0002\" name=\"A Name\" fixed=\"0\" occurenceMultiplier=\"1\" occurence=\"32\" >\n" // krazy:exclude=spelling
" <PAYMENTS>\n"
" <PAYMENT date=\"%3\" />\n"
" </PAYMENTS>\n"
" <TRANSACTION postdate=\"\" memo=\"Wohnung:Miete\" id=\"\" commodity=\"EUR\" entrydate=\"\" >\n"
" <SPLITS>\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"\" number=\"\" reconcileflag=\"1\" memo=\"\" value=\"-96379/100\" account=\"A000276\" />\n"
" </SPLITS>\n"
" <KEYVALUEPAIRS>\n"
" <PAIR key=\"key\" value=\"value\" />\n"
" </KEYVALUEPAIRS>\n"
" </TRANSACTION>\n"
" </SCHEDULED_TX>\n"
"</SCHEDULE-CONTAINER>\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(
"<!DOCTYPE TEST>\n"
"<SCHEDULE-CONTAINER>\n"
"<SCHEDULED_TX startDate=\"2007-02-17\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH000058\" name=\"Car Tax\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"16384\" >\n" // krazy:exclude=spelling
" <PAYMENTS/>\n"
" <TRANSACTION postdate=\"\" memo=\"\" id=\"\" commodity=\"GBP\" entrydate=\"\" >\n"
" <SPLITS>\n"
" <SPLIT payee=\"P000044\" reconciledate=\"\" shares=\"-15000/100\" action=\"Withdrawal\" bankid=\"\" number=\"\" reconcileflag=\"0\" memo=\"\" value=\"-15000/100\" account=\"A000155\" />\n"
" <SPLIT payee=\"\" reconciledate=\"\" shares=\"15000/100\" action=\"Withdrawal\" bankid=\"\" number=\"\" reconcileflag=\"0\" memo=\"\" value=\"15000/100\" account=\"A000182\" />\n"
" </SPLITS>\n"
" <KEYVALUEPAIRS/>\n"
" </TRANSACTION>\n"
"</SCHEDULED_TX>\n"
"</SCHEDULE-CONTAINER>\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(
"<!DOCTYPE TEST>\n"
"<SCHEDULE-CONTAINER>\n"
"<SCHEDULED_TX startDate=\"2014-10-31\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH000058\" name=\"Car Tax\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"32\" >\n" // krazy:exclude=spelling
" <PAYMENTS/>\n"
" <TRANSACTION postdate=\"\" memo=\"\" id=\"\" commodity=\"GBP\" entrydate=\"\" >\n"
" <SPLITS>\n"
" <SPLIT payee=\"P000044\" reconciledate=\"\" shares=\"-15000/100\" action=\"Withdrawal\" bankid=\"\" number=\"\" reconcileflag=\"0\" memo=\"\" value=\"-15000/100\" account=\"A000155\" />\n"
" <SPLIT payee=\"\" reconciledate=\"\" shares=\"15000/100\" action=\"Withdrawal\" bankid=\"\" number=\"\" reconcileflag=\"0\" memo=\"\" value=\"15000/100\" account=\"A000182\" />\n"
" </SPLITS>\n"
" <KEYVALUEPAIRS/>\n"
" </TRANSACTION>\n"
"</SCHEDULED_TX>\n"
"</SCHEDULE-CONTAINER>\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 OCCUR_EVERYHALFMONTH using nextPayment
+ // Test a Schedule with occurrence EveryHalfMonth using nextPayment
MyMoneySchedule s;
s.setStartDate(QDate(2007, 1, 1));
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ 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(
"<!DOCTYPE TEST>\n"
"<SCHEDULE-CONTAINER>\n"
"<SCHEDULED_TX startDate=\"2003-12-31\" autoEnter=\"1\" weekendOption=\"0\" lastPayment=\"2006-01-31\" paymentType=\"2\" endDate=\"\" type=\"2\" id=\"SCH000032\" name=\"DSL\" fixed=\"0\" occurenceMultiplier=\"1\" occurence=\"32\" >\n" // krazy:exclude=spelling
" <PAYMENTS/>\n"
" <TRANSACTION postdate=\"2006-02-28\" memo=\"\" id=\"\" commodity=\"EUR\" entrydate=\"\" >\n"
" <SPLITS>\n"
" <SPLIT payee=\"P000076\" reconciledate=\"\" shares=\"1200/100\" action=\"Deposit\" bankid=\"\" number=\"\" reconcileflag=\"0\" memo=\"\" value=\"1200/100\" account=\"A000076\" />\n"
" <SPLIT payee=\"\" reconciledate=\"\" shares=\"-1200/100\" action=\"Deposit\" bankid=\"\" number=\"\" reconcileflag=\"0\" memo=\"\" value=\"-1200/100\" account=\"A000009\" />\n"
" </SPLITS>\n"
" <KEYVALUEPAIRS/>\n"
" </TRANSACTION>\n"
"</SCHEDULED_TX>\n"
"</SCHEDULE-CONTAINER>\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<QDate> list = sch.paymentDates(nextPayment, endDate);
QVERIFY(list.count() == 3);
QVERIFY(list[0] == QDate(2006, 2, 28));
QVERIFY(list[1] == QDate(2006, 3, 31));
// Would fall on a Sunday so gets moved back to 28th.
QVERIFY(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
- // MyMoneySchedule::OCCUR_ONCE
- sch.setOccurrence(MyMoneySchedule::OCCUR_ONCE);
+ // 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);
QVERIFY(list.count() == 1);
QVERIFY(list[0] == QDate(2009, 1, 1));
- // MyMoneySchedule::OCCUR_DAILY
- sch.setOccurrence(MyMoneySchedule::OCCUR_DAILY);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2009, 1, 1));
QVERIFY(list[1] == QDate(2009, 1, 2));
// Would fall on Saturday so gets moved to 2nd.
QVERIFY(list[2] == QDate(2009, 1, 2));
// Would fall on Sunday so gets moved to 2nd.
QVERIFY(list[3] == QDate(2009, 1, 2));
QVERIFY(list[4] == QDate(2009, 1, 5));
- // MyMoneySchedule::OCCUR_DAILY with multiplier 2
+ // Schedule::Occurrence::Daily with multiplier 2
sch.setOccurrenceMultiplier(2);
list = sch.paymentDates(startDate.addDays(1), endDate);
QVERIFY(list.count() == 2);
// Would fall on Sunday so gets moved to 2nd.
QVERIFY(list[0] == QDate(2009, 1, 2));
QVERIFY(list[1] == QDate(2009, 1, 5));
sch.setOccurrenceMultiplier(1);
- // MyMoneySchedule::OCCUR_WEEKLY
- sch.setOccurrence(MyMoneySchedule::OCCUR_WEEKLY);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2009, 1, 6));
QVERIFY(list[1] == QDate(2009, 1, 13));
QVERIFY(list[2] == QDate(2009, 1, 20));
QVERIFY(list[3] == QDate(2009, 1, 27));
QVERIFY(list[4] == QDate(2009, 2, 3));
- // MyMoneySchedule::OCCUR_EVERYOTHERWEEK
- sch.setOccurrence(MyMoneySchedule::OCCUR_EVERYOTHERWEEK);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2009, 2, 5));
QVERIFY(list[1] == QDate(2009, 2, 19));
QVERIFY(list[2] == QDate(2009, 3, 5));
QVERIFY(list[3] == QDate(2009, 3, 19));
QVERIFY(list[4] == QDate(2009, 4, 2));
- // MyMoneySchedule::OCCUR_FORTNIGHTLY
- sch.setOccurrence(MyMoneySchedule::OCCUR_FORTNIGHTLY);
+ // 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);
QVERIFY(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.
QVERIFY(list[0] == QDate(2009, 4, 17));
// Would fall on a Saturday so gets moved to 1st.
QVERIFY(list[1] == QDate(2009, 5, 1));
// Would fall on a Saturday so gets moved to 15th.
QVERIFY(list[2] == QDate(2009, 5, 15));
// Would fall on a Saturday so gets moved to 29th.
QVERIFY(list[3] == QDate(2009, 5, 29));
- // MyMoneySchedule::OCCUR_EVERYHALFMONTH
- sch.setOccurrence(MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2009, 6, 1));
QVERIFY(list[1] == QDate(2009, 6, 16));
QVERIFY(list[2] == QDate(2009, 7, 1));
QVERIFY(list[3] == QDate(2009, 7, 16));
// Would fall on a Saturday so gets moved to 31st.
QVERIFY(list[4] == QDate(2009, 7, 31));
- // MyMoneySchedule::OCCUR_EVERYTHREEWEEKS
- sch.setOccurrence(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2009, 8, 12));
QVERIFY(list[1] == QDate(2009, 9, 2));
QVERIFY(list[2] == QDate(2009, 9, 23));
QVERIFY(list[3] == QDate(2009, 10, 14));
QVERIFY(list[4] == QDate(2009, 11, 4));
- // MyMoneySchedule::OCCUR_EVERYFOURWEEKS
- sch.setOccurrence(MyMoneySchedule::OCCUR_EVERYFOURWEEKS);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2009, 11, 13));
QVERIFY(list[1] == QDate(2009, 12, 11));
QVERIFY(list[2] == QDate(2010, 1, 8));
QVERIFY(list[3] == QDate(2010, 2, 5));
QVERIFY(list[4] == QDate(2010, 3, 5));
- // MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS
- sch.setOccurrence(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2010, 3, 19));
// Would fall on a Sunday so gets moved to 16th.
QVERIFY(list[1] == QDate(2010, 4, 16));
QVERIFY(list[2] == QDate(2010, 5, 18));
QVERIFY(list[3] == QDate(2010, 6, 17));
// Would fall on a Saturday so gets moved to 16th.
QVERIFY(list[4] == QDate(2010, 7, 16));
- // MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS
- sch.setOccurrence(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2010, 7, 26));
QVERIFY(list[1] == QDate(2010, 9, 20));
QVERIFY(list[2] == QDate(2010, 11, 15));
QVERIFY(list[3] == QDate(2011, 1, 10));
QVERIFY(list[4] == QDate(2011, 3, 7));
- // MyMoneySchedule::OCCUR_EVERYOTHERMONTH
- sch.setOccurrence(MyMoneySchedule::OCCUR_EVERYOTHERMONTH);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2011, 3, 14));
// Would fall on a Saturday so gets moved to 13th.
QVERIFY(list[1] == QDate(2011, 5, 13));
QVERIFY(list[2] == QDate(2011, 7, 14));
QVERIFY(list[3] == QDate(2011, 9, 14));
QVERIFY(list[4] == QDate(2011, 11, 14));
- // MyMoneySchedule::OCCUR_EVERYTHREEMONTHS
- sch.setOccurrence(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2011, 11, 15));
QVERIFY(list[1] == QDate(2012, 2, 15));
QVERIFY(list[2] == QDate(2012, 5, 15));
QVERIFY(list[3] == QDate(2012, 8, 15));
QVERIFY(list[4] == QDate(2012, 11, 15));
- // MyMoneySchedule::OCCUR_QUARTERLY
- sch.setOccurrence(MyMoneySchedule::OCCUR_QUARTERLY);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2012, 11, 20));
QVERIFY(list[1] == QDate(2013, 2, 20));
QVERIFY(list[2] == QDate(2013, 5, 20));
QVERIFY(list[3] == QDate(2013, 8, 20));
QVERIFY(list[4] == QDate(2013, 11, 20));
- // MyMoneySchedule::OCCUR_EVERYFOURMONTHS
- sch.setOccurrence(MyMoneySchedule::OCCUR_EVERYFOURMONTHS);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2013, 11, 21));
QVERIFY(list[1] == QDate(2014, 3, 21));
QVERIFY(list[2] == QDate(2014, 7, 21));
QVERIFY(list[3] == QDate(2014, 11, 21));
// Would fall on a Saturday so gets moved to 20th.
QVERIFY(list[4] == QDate(2015, 3, 20));
- // MyMoneySchedule::OCCUR_TWICEYEARLY
- sch.setOccurrence(MyMoneySchedule::OCCUR_TWICEYEARLY);
+ // 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);
QVERIFY(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.
QVERIFY(list[0] == QDate(2015, 9, 22));
QVERIFY(list[1] == QDate(2016, 3, 22));
QVERIFY(list[2] == QDate(2016, 9, 22));
QVERIFY(list[3] == QDate(2017, 3, 22));
- // MyMoneySchedule::OCCUR_YEARLY
- sch.setOccurrence(MyMoneySchedule::OCCUR_YEARLY);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2017, 3, 23));
QVERIFY(list[1] == QDate(2018, 3, 23));
// Would fall on a Saturday so gets moved to 22nd.
QVERIFY(list[2] == QDate(2019, 3, 22));
QVERIFY(list[3] == QDate(2020, 3, 23));
QVERIFY(list[4] == QDate(2021, 3, 23));
- // MyMoneySchedule::OCCUR_EVERYOTHERYEAR
- sch.setOccurrence(MyMoneySchedule::OCCUR_EVERYOTHERYEAR);
+ // 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);
QVERIFY(list.count() == 5);
QVERIFY(list[0] == QDate(2021, 3, 24));
QVERIFY(list[1] == QDate(2023, 3, 24));
QVERIFY(list[2] == QDate(2025, 3, 24));
QVERIFY(list[3] == QDate(2027, 3, 24));
// Would fall on a Saturday so gets moved to 23rd.
QVERIFY(list[4] == QDate(2029, 3, 23));
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
}
void MyMoneyScheduleTest::testWriteXML()
{
MyMoneySchedule sch("A Name",
- MyMoneySchedule::TYPE_BILL,
- MyMoneySchedule::OCCUR_WEEKLY, 123,
- MyMoneySchedule::STYPE_DIRECTDEBIT,
+ Schedule::Type::Bill,
+ Schedule::Occurrence::Weekly, 123,
+ Schedule::PaymentType::DirectDebit,
QDate::currentDate(),
QDate(),
true,
true);
sch.setLastPayment(QDate::currentDate());
sch.recordPayment(QDate::currentDate());
sch.setId("SCH0001");
MyMoneyTransaction t;
t.setPostDate(QDate(2001, 12, 28));
t.setEntryDate(QDate(2003, 9, 29));
t.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(MyMoneySplit::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(MyMoneySplit::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"));
QVERIFY(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"));
QCOMPARE(schedule.attribute("startDate"), QDate::currentDate().toString(Qt::ISODate));
QCOMPARE(schedule.attribute("lastPayment"), QDate::currentDate().toString(Qt::ISODate));
QCOMPARE(schedule.attribute("occurenceMultiplier"), QLatin1String("123"));
QCOMPARE(schedule.attribute("occurence"), QLatin1String("4"));
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(
"<!DOCTYPE TEST>\n"
"<SCHEDULE-CONTAINER>\n"
" <SCHEDULED_TX startDate=\"%1\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"%2\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH0002\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"4\" >\n" // krazy:exclude=spelling
" <PAYMENTS>\n"
" <PAYMENT date=\"%3\" />\n"
" </PAYMENTS>\n"
" <TRANSACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"\" commodity=\"EUR\" entrydate=\"2003-09-29\" >\n"
" <SPLITS>\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"\" bankid=\"SPID1\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"\" bankid=\"SPID2\" number=\"\" reconcileflag=\"1\" memo=\"\" value=\"-96379/100\" account=\"A000276\" />\n"
" </SPLITS>\n"
" <KEYVALUEPAIRS>\n"
" <PAIR key=\"key\" value=\"value\" />\n"
" </KEYVALUEPAIRS>\n"
" </TRANSACTION>\n"
" </SCHEDULED_TX>\n"
"</SCHEDULE-CONTAINER>\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(
"<!DOCTYPE TEST>\n"
"<SCHEDULE-CONTAINER>\n"
" <SCHEDULED_TX startDate=\"%1\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"%2\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH0002\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"4\" >\n" // krazy:exclude=spelling
" <PAYMENTS>\n"
" <PAYMENT date=\"%3\" />\n"
" </PAYMENTS>\n"
" <TRANSACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"\" commodity=\"EUR\" entrydate=\"\" >\n"
" <SPLITS>\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"\" bankid=\"SPID1\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"\" bankid=\"SPID2\" number=\"\" reconcileflag=\"1\" memo=\"\" value=\"-96379/100\" account=\"A000276\" />\n"
" </SPLITS>\n"
" <KEYVALUEPAIRS>\n"
" <PAIR key=\"key\" value=\"value\" />\n"
" </KEYVALUEPAIRS>\n"
" </TRANSACTION>\n"
" </SCHEDULED_TX>\n"
"</SCHEDULE-CONTAINER>\n"
).arg(QDate::currentDate().toString(Qt::ISODate))
.arg(QDate::currentDate().toString(Qt::ISODate))
.arg(QDate::currentDate().toString(Qt::ISODate));
QString ref_false = QString(
"<!DOCTYPE TEST>\n"
"<SCHEDULE-CONTAINER>\n"
" <SCHEDULE startDate=\"%1\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"%2\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH0002\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"4\" >\n" // krazy:exclude=spelling
" <PAYMENTS count=\"1\" >\n"
" <PAYMENT date=\"%3\" />\n"
" </PAYMENTS>\n"
" <TRANSACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"\" commodity=\"EUR\" entrydate=\"2003-09-29\" >\n"
" <SPLITS>\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"\" bankid=\"SPID1\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"\" bankid=\"SPID2\" number=\"\" reconcileflag=\"1\" memo=\"\" value=\"-96379/100\" account=\"A000276\" />\n"
" </SPLITS>\n"
" <KEYVALUEPAIRS>\n"
" <PAIR key=\"key\" value=\"value\" />\n"
" </KEYVALUEPAIRS>\n"
" </TRANSACTION>\n"
" </SCHEDULED_TX>\n"
"</SCHEDULE-CONTAINER>\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);
QVERIFY(sch.id() == "SCH0002");
QVERIFY(sch.nextDueDate() == QDate::currentDate().addDays(7));
QVERIFY(sch.startDate() == QDate::currentDate());
QVERIFY(sch.endDate() == QDate());
QVERIFY(sch.autoEnter() == true);
QVERIFY(sch.isFixed() == true);
- QVERIFY(sch.weekendOption() == MyMoneySchedule::MoveNothing);
+ QVERIFY(sch.weekendOption() == Schedule::WeekendOption::MoveNothing);
QVERIFY(sch.lastPayment() == QDate::currentDate());
- QVERIFY(sch.paymentType() == MyMoneySchedule::STYPE_DIRECTDEBIT);
- QVERIFY(sch.type() == MyMoneySchedule::TYPE_BILL);
+ QVERIFY(sch.paymentType() == Schedule::PaymentType::DirectDebit);
+ QVERIFY(sch.type() == Schedule::Type::Bill);
QVERIFY(sch.name() == "A Name");
- QVERIFY(sch.occurrence() == MyMoneySchedule::OCCUR_WEEKLY);
+ QVERIFY(sch.occurrence() == Schedule::Occurrence::Weekly);
QVERIFY(sch.occurrenceMultiplier() == 1);
QVERIFY(sch.nextDueDate() == sch.lastPayment().addDays(7));
QVERIFY(sch.recordedPayments().count() == 1);
QVERIFY(sch.recordedPayments()[0] == QDate::currentDate());
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
doc.setContent(ref_ok2);
node = doc.documentElement().firstChild().toElement();
try {
sch = MyMoneySchedule(node);
QVERIFY(sch.id() == "SCH0002");
QVERIFY(sch.nextDueDate() == QDate::currentDate().addDays(7));
QVERIFY(sch.startDate() == QDate::currentDate());
QVERIFY(sch.endDate() == QDate());
QVERIFY(sch.autoEnter() == true);
QVERIFY(sch.isFixed() == true);
- QVERIFY(sch.weekendOption() == MyMoneySchedule::MoveNothing);
+ QVERIFY(sch.weekendOption() == Schedule::WeekendOption::MoveNothing);
QVERIFY(sch.lastPayment() == QDate::currentDate());
- QVERIFY(sch.paymentType() == MyMoneySchedule::STYPE_DIRECTDEBIT);
- QVERIFY(sch.type() == MyMoneySchedule::TYPE_BILL);
+ QVERIFY(sch.paymentType() == Schedule::PaymentType::DirectDebit);
+ QVERIFY(sch.type() == Schedule::Type::Bill);
QVERIFY(sch.name() == "A Name");
- QVERIFY(sch.occurrence() == MyMoneySchedule::OCCUR_WEEKLY);
+ QVERIFY(sch.occurrence() == Schedule::Occurrence::Weekly);
QVERIFY(sch.occurrenceMultiplier() == 1);
QVERIFY(sch.nextDueDate() == sch.lastPayment().addDays(7));
QVERIFY(sch.recordedPayments().count() == 1);
QVERIFY(sch.recordedPayments()[0] == QDate::currentDate());
} catch (const MyMoneyException &) {
QFAIL("Unexpected exception");
}
}
void MyMoneyScheduleTest::testHasReferenceTo()
{
MyMoneySchedule sch;
QString ref_ok = QString(
"<!DOCTYPE TEST>\n"
"<SCHEDULE-CONTAINER>\n"
" <SCHEDULED_TX startDate=\"%1\" autoEnter=\"1\" weekendOption=\"2\" lastPayment=\"%2\" paymentType=\"1\" endDate=\"\" type=\"1\" id=\"SCH0002\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"4\" >\n" // krazy:exclude=spelling
" <PAYMENTS>\n"
" <PAYMENT date=\"%3\" />\n"
" </PAYMENTS>\n"
" <TRANSACTION postdate=\"2001-12-28\" memo=\"Wohnung:Miete\" id=\"\" commodity=\"EUR\" entrydate=\"2003-09-29\" >\n"
" <SPLITS>\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" account=\"A000076\" />\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"\" number=\"\" reconcileflag=\"1\" memo=\"\" value=\"-96379/100\" account=\"A000276\" />\n"
" </SPLITS>\n"
" <KEYVALUEPAIRS>\n"
" <PAIR key=\"key\" value=\"value\" />\n"
" </KEYVALUEPAIRS>\n"
" </TRANSACTION>\n"
" </SCHEDULED_TX>\n"
"</SCHEDULE-CONTAINER>\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");
}
QVERIFY(sch.hasReferenceTo("P000001") == true);
QVERIFY(sch.hasReferenceTo("A000276") == true);
QVERIFY(sch.hasReferenceTo("A000076") == true);
QVERIFY(sch.hasReferenceTo("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(MyMoneySchedule::MoveNothing);
+ s.setWeekendOption(Schedule::WeekendOption::MoveNothing);
QVERIFY(s.adjustedNextDueDate() == dueDate);
- s.setWeekendOption(MyMoneySchedule::MoveBefore);
+ s.setWeekendOption(Schedule::WeekendOption::MoveBefore);
switch (i) {
case 5: // Saturday
case 6: // Sunday
QVERIFY(s.adjustedNextDueDate() == QDate(2007, 9, 7));
break;
default:
QVERIFY(s.adjustedNextDueDate() == dueDate);
break;
}
- s.setWeekendOption(MyMoneySchedule::MoveAfter);
+ s.setWeekendOption(Schedule::WeekendOption::MoveAfter);
switch (i) {
case 5: // Saturday
case 6: // Sunday
QVERIFY(s.adjustedNextDueDate() == QDate(2007, 9, 10));
break;
default:
QVERIFY(s.adjustedNextDueDate() == dueDate);
break;
}
dueDate = dueDate.addDays(1);
}
}
void MyMoneyScheduleTest::testModifyNextDueDate()
{
MyMoneySchedule s;
s.setStartDate(QDate(2007, 1, 2));
- s.setOccurrence(MyMoneySchedule::OCCUR_MONTHLY);
+ s.setOccurrence(Schedule::Occurrence::Monthly);
s.setNextDueDate(s.startDate().addMonths(1));
s.setLastPayment(s.startDate());
QList<QDate> 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(MyMoneySchedule::OCCUR_ONCE), 0);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_DAILY), 1);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_WEEKLY), 7);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYOTHERWEEK), 14);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_FORTNIGHTLY), 14);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYHALFMONTH), 15);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS), 21);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYFOURWEEKS), 28);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS), 30);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_MONTHLY), 30);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS), 56);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYOTHERMONTH), 60);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS), 90);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_QUARTERLY), 90);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYFOURMONTHS), 120);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_TWICEYEARLY), 180);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_YEARLY), 360);
- QCOMPARE(MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::OCCUR_EVERYOTHERYEAR), 0);
+ 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(MyMoneySchedule::OCCUR_ONCE), 0);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_DAILY), 365);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_WEEKLY), 52);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYOTHERWEEK), 26);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_FORTNIGHTLY), 26);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYHALFMONTH), 24);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS), 17);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYFOURWEEKS), 13);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS), 12);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_MONTHLY), 12);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS), 6);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYOTHERMONTH), 6);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS), 4);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_QUARTERLY), 4);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYFOURMONTHS), 3);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_TWICEYEARLY), 2);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_YEARLY), 1);
- QCOMPARE(MyMoneySchedule::eventsPerYear(MyMoneySchedule::OCCUR_EVERYOTHERYEAR), 0);
+ 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(MyMoneySchedule::OCCUR_ONCE), QLatin1String("Once"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_DAILY), QLatin1String("Daily"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_WEEKLY), QLatin1String("Weekly"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYOTHERWEEK), QLatin1String("Every other week"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_FORTNIGHTLY), QLatin1String("Fortnightly"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYHALFMONTH), QLatin1String("Every half month"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS), QLatin1String("Every three weeks"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYFOURWEEKS), QLatin1String("Every four weeks"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS), QLatin1String("Every thirty days"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_MONTHLY), QLatin1String("Monthly"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS), QLatin1String("Every eight weeks"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYOTHERMONTH), QLatin1String("Every two months"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS), QLatin1String("Every three months"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_QUARTERLY), QLatin1String("Quarterly"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYFOURMONTHS), QLatin1String("Every four months"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_TWICEYEARLY), QLatin1String("Twice yearly"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_YEARLY), QLatin1String("Yearly"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYOTHERYEAR), QLatin1String("Every other year"));
+ 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(MyMoneySchedule::OCCUR_ONCE); QCOMPARE(s.occurrenceToString(), QLatin1String("Once"));
- s.setOccurrence(MyMoneySchedule::OCCUR_DAILY); QCOMPARE(s.occurrenceToString(), QLatin1String("Daily"));
- s.setOccurrence(MyMoneySchedule::OCCUR_WEEKLY); QCOMPARE(s.occurrenceToString(), QLatin1String("Weekly"));
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYOTHERWEEK); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other week"));
+ 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(MyMoneySchedule::OCCUR_FORTNIGHTLY); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other week"));
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYHALFMONTH); QCOMPARE(s.occurrenceToString(), QLatin1String("Every half month"));
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three weeks"));
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYFOURWEEKS); QCOMPARE(s.occurrenceToString(), QLatin1String("Every four weeks"));
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS); QCOMPARE(s.occurrenceToString(), QLatin1String("Every thirty days"));
- s.setOccurrence(MyMoneySchedule::OCCUR_MONTHLY); QCOMPARE(s.occurrenceToString(), QLatin1String("Monthly"));
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS); QCOMPARE(s.occurrenceToString(), QLatin1String("Every eight weeks"));
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYOTHERMONTH); QCOMPARE(s.occurrenceToString(), QLatin1String("Every two months"));
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three months"));
+ 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(MyMoneySchedule::OCCUR_QUARTERLY); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three months"));
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYFOURMONTHS); QCOMPARE(s.occurrenceToString(), QLatin1String("Every four months"));
- s.setOccurrence(MyMoneySchedule::OCCUR_TWICEYEARLY); QCOMPARE(s.occurrenceToString(), QLatin1String("Twice yearly"));
- s.setOccurrence(MyMoneySchedule::OCCUR_YEARLY); QCOMPARE(s.occurrenceToString(), QLatin1String("Yearly"));
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYOTHERYEAR); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other year"));
+ 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(MyMoneySchedule::OCCUR_ONCE), MyMoneySchedule::occurrenceToString(1, MyMoneySchedule::OCCUR_ONCE));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_DAILY), MyMoneySchedule::occurrenceToString(1, MyMoneySchedule::OCCUR_DAILY));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_WEEKLY), MyMoneySchedule::occurrenceToString(1, MyMoneySchedule::OCCUR_WEEKLY));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYOTHERWEEK), MyMoneySchedule::occurrenceToString(2, MyMoneySchedule::OCCUR_WEEKLY));
- // OCCUR_FORTNIGHTLY will no longer be used: only Every Other Week
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYHALFMONTH), MyMoneySchedule::occurrenceToString(1, MyMoneySchedule::OCCUR_EVERYHALFMONTH));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS), MyMoneySchedule::occurrenceToString(3, MyMoneySchedule::OCCUR_WEEKLY));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYFOURWEEKS), MyMoneySchedule::occurrenceToString(4, MyMoneySchedule::OCCUR_WEEKLY));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_MONTHLY), MyMoneySchedule::occurrenceToString(1, MyMoneySchedule::OCCUR_MONTHLY));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS), MyMoneySchedule::occurrenceToString(8, MyMoneySchedule::OCCUR_WEEKLY));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYOTHERMONTH), MyMoneySchedule::occurrenceToString(2, MyMoneySchedule::OCCUR_MONTHLY));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS), MyMoneySchedule::occurrenceToString(3, MyMoneySchedule::OCCUR_MONTHLY));
- // OCCUR_QUARTERLY will no longer be used: only Every Three Months
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYFOURMONTHS), MyMoneySchedule::occurrenceToString(4, MyMoneySchedule::OCCUR_MONTHLY));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_TWICEYEARLY), MyMoneySchedule::occurrenceToString(6, MyMoneySchedule::OCCUR_MONTHLY));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_YEARLY), MyMoneySchedule::occurrenceToString(1, MyMoneySchedule::OCCUR_YEARLY));
- QCOMPARE(MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYOTHERYEAR), MyMoneySchedule::occurrenceToString(2, MyMoneySchedule::OCCUR_YEARLY));
+ 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, MyMoneySchedule::OCCUR_ONCE), QLatin1String("2 times"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(2, MyMoneySchedule::OCCUR_DAILY), QLatin1String("Every 2 days"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(5, MyMoneySchedule::OCCUR_WEEKLY), QLatin1String("Every 5 weeks"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(2, MyMoneySchedule::OCCUR_EVERYHALFMONTH), QLatin1String("Every 2 half months"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(5, MyMoneySchedule::OCCUR_MONTHLY), QLatin1String("Every 5 months"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(3, MyMoneySchedule::OCCUR_YEARLY), QLatin1String("Every 3 years"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(37, MyMoneySchedule::OCCUR_ONCE), QLatin1String("37 times"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(43, MyMoneySchedule::OCCUR_DAILY), QLatin1String("Every 43 days"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(61, MyMoneySchedule::OCCUR_WEEKLY), QLatin1String("Every 61 weeks"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(73, MyMoneySchedule::OCCUR_EVERYHALFMONTH), QLatin1String("Every 73 half months"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(83, MyMoneySchedule::OCCUR_MONTHLY), QLatin1String("Every 83 months"));
- QCOMPARE(MyMoneySchedule::occurrenceToString(89, MyMoneySchedule::OCCUR_YEARLY), QLatin1String("Every 89 years"));
+ 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(MyMoneySchedule::OCCUR_ONCE); s.setOccurrenceMultiplier(1);
- s.setOccurrence(MyMoneySchedule::OCCUR_ONCE);
+ 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(MyMoneySchedule::OCCUR_DAILY);
+ 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(MyMoneySchedule::OCCUR_WEEKLY);
+ 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(MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ 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(MyMoneySchedule::OCCUR_MONTHLY);
+ 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(MyMoneySchedule::OCCUR_YEARLY);
+ 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(MyMoneySchedule::OCCUR_ONCE), QLatin1String("Once"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_DAILY), QLatin1String("Day"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_WEEKLY), QLatin1String("Week"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_EVERYHALFMONTH), QLatin1String("Half-month"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_MONTHLY), QLatin1String("Month"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_YEARLY), QLatin1String("Year"));
+ 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(MyMoneySchedule::OCCUR_EVERYOTHERWEEK), QLatin1String("Any"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_FORTNIGHTLY), QLatin1String("Any"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS), QLatin1String("Any"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_EVERYFOURWEEKS), QLatin1String("Any"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS), QLatin1String("Any"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS), QLatin1String("Any"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_EVERYOTHERMONTH), QLatin1String("Any"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS), QLatin1String("Any"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_QUARTERLY), QLatin1String("Any"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_EVERYFOURMONTHS), QLatin1String("Any"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_TWICEYEARLY), QLatin1String("Any"));
- QCOMPARE(MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_EVERYOTHERYEAR), QLatin1String("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(MyMoneySchedule::OCCUR_ONCE);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_ONCE);
+ s.setOccurrencePeriod(Schedule::Occurrence::Once);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once);
s.setOccurrenceMultiplier(1);
QCOMPARE(s.occurrenceMultiplier(), 1);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_ONCE);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_ONCE);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Once);
s.setOccurrenceMultiplier(2);
QCOMPARE(s.occurrenceMultiplier(), 2);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_ONCE);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_ONCE);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Once);
- s.setOccurrencePeriod(MyMoneySchedule::OCCUR_DAILY);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_DAILY);
+ s.setOccurrencePeriod(Schedule::Occurrence::Daily);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily);
s.setOccurrenceMultiplier(1);
QCOMPARE(s.occurrenceMultiplier(), 1);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_DAILY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_DAILY);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Daily);
s.setOccurrenceMultiplier(30);
QCOMPARE(s.occurrenceMultiplier(), 30);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_DAILY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThirtyDays);
s.setOccurrenceMultiplier(2);
QCOMPARE(s.occurrenceMultiplier(), 2);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_DAILY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_DAILY);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Daily);
- s.setOccurrencePeriod(MyMoneySchedule::OCCUR_WEEKLY);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
+ s.setOccurrencePeriod(Schedule::Occurrence::Weekly);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
s.setOccurrenceMultiplier(1);
QCOMPARE(s.occurrenceMultiplier(), 1);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_WEEKLY);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly);
s.setOccurrenceMultiplier(2);
QCOMPARE(s.occurrenceMultiplier(), 2);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYOTHERWEEK);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherWeek);
s.setOccurrenceMultiplier(3);
QCOMPARE(s.occurrenceMultiplier(), 3);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYTHREEWEEKS);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeWeeks);
s.setOccurrenceMultiplier(4);
QCOMPARE(s.occurrenceMultiplier(), 4);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYFOURWEEKS);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourWeeks);
s.setOccurrenceMultiplier(5);
QCOMPARE(s.occurrenceMultiplier(), 5);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_WEEKLY);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly);
s.setOccurrenceMultiplier(8);
QCOMPARE(s.occurrenceMultiplier(), 8);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryEightWeeks);
- s.setOccurrencePeriod(MyMoneySchedule::OCCUR_EVERYHALFMONTH);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ s.setOccurrencePeriod(Schedule::Occurrence::EveryHalfMonth);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth);
s.setOccurrenceMultiplier(1);
QCOMPARE(s.occurrenceMultiplier(), 1);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_EVERYHALFMONTH);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryHalfMonth);
s.setOccurrenceMultiplier(2);
QCOMPARE(s.occurrenceMultiplier(), 2);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_EVERYHALFMONTH);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryHalfMonth);
- s.setOccurrencePeriod(MyMoneySchedule::OCCUR_MONTHLY);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
+ s.setOccurrencePeriod(Schedule::Occurrence::Monthly);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
s.setOccurrenceMultiplier(1);
QCOMPARE(s.occurrenceMultiplier(), 1);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_MONTHLY);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Monthly);
s.setOccurrenceMultiplier(2);
QCOMPARE(s.occurrenceMultiplier(), 2);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYOTHERMONTH);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherMonth);
s.setOccurrenceMultiplier(3);
QCOMPARE(s.occurrenceMultiplier(), 3);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYTHREEMONTHS);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeMonths);
s.setOccurrenceMultiplier(4);
QCOMPARE(s.occurrenceMultiplier(), 4);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYFOURMONTHS);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourMonths);
s.setOccurrenceMultiplier(5);
QCOMPARE(s.occurrenceMultiplier(), 5);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_MONTHLY);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Monthly);
s.setOccurrenceMultiplier(6);
QCOMPARE(s.occurrenceMultiplier(), 6);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_TWICEYEARLY);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::TwiceYearly);
- s.setOccurrencePeriod(MyMoneySchedule::OCCUR_YEARLY);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_YEARLY);
+ s.setOccurrencePeriod(Schedule::Occurrence::Yearly);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly);
s.setOccurrenceMultiplier(1);
QCOMPARE(s.occurrenceMultiplier(), 1);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_YEARLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_YEARLY);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Yearly);
s.setOccurrenceMultiplier(2);
QCOMPARE(s.occurrenceMultiplier(), 2);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_YEARLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYOTHERYEAR);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherYear);
s.setOccurrenceMultiplier(3);
QCOMPARE(s.occurrenceMultiplier(), 3);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_YEARLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_YEARLY);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Yearly);
// Set occurrence: check occurrence, Period and Multiplier
- s.setOccurrence(MyMoneySchedule::OCCUR_ONCE);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_ONCE);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_ONCE);
+ s.setOccurrence(Schedule::Occurrence::Once);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Once);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once);
QCOMPARE(s.occurrenceMultiplier(), 1);
- s.setOccurrence(MyMoneySchedule::OCCUR_DAILY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_DAILY);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_DAILY);
+ s.setOccurrence(Schedule::Occurrence::Daily);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Daily);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily);
QCOMPARE(s.occurrenceMultiplier(), 1);
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_DAILY);
+ s.setOccurrence(Schedule::Occurrence::EveryThirtyDays);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThirtyDays);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily);
QCOMPARE(s.occurrenceMultiplier(), 30);
- s.setOccurrence(MyMoneySchedule::OCCUR_WEEKLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_WEEKLY);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
+ s.setOccurrence(Schedule::Occurrence::Weekly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
QCOMPARE(s.occurrenceMultiplier(), 1);
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYOTHERWEEK);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYOTHERWEEK);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
+ 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(MyMoneySchedule::OCCUR_FORTNIGHTLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYOTHERWEEK);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
+ s.setOccurrence(Schedule::Occurrence::Fortnightly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherWeek);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
QCOMPARE(s.occurrenceMultiplier(), 2);
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYTHREEWEEKS);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
+ s.setOccurrence(Schedule::Occurrence::EveryThreeWeeks);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeWeeks);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
QCOMPARE(s.occurrenceMultiplier(), 3);
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYFOURWEEKS);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYFOURWEEKS);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
+ s.setOccurrence(Schedule::Occurrence::EveryFourWeeks);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourWeeks);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
QCOMPARE(s.occurrenceMultiplier(), 4);
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_WEEKLY);
+ s.setOccurrence(Schedule::Occurrence::EveryEightWeeks);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryEightWeeks);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly);
QCOMPARE(s.occurrenceMultiplier(), 8);
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYHALFMONTH);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYHALFMONTH);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_EVERYHALFMONTH);
+ s.setOccurrence(Schedule::Occurrence::EveryHalfMonth);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryHalfMonth);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth);
QCOMPARE(s.occurrenceMultiplier(), 1);
- s.setOccurrence(MyMoneySchedule::OCCUR_MONTHLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_MONTHLY);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
+ s.setOccurrence(Schedule::Occurrence::Monthly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Monthly);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
QCOMPARE(s.occurrenceMultiplier(), 1);
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYOTHERMONTH);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYOTHERMONTH);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
+ s.setOccurrence(Schedule::Occurrence::EveryOtherMonth);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherMonth);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
QCOMPARE(s.occurrenceMultiplier(), 2);
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYTHREEMONTHS);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
+ 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(MyMoneySchedule::OCCUR_QUARTERLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYTHREEMONTHS);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
+ s.setOccurrence(Schedule::Occurrence::Quarterly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeMonths);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
QCOMPARE(s.occurrenceMultiplier(), 3);
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYFOURMONTHS);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYFOURMONTHS);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
+ s.setOccurrence(Schedule::Occurrence::EveryFourMonths);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourMonths);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
QCOMPARE(s.occurrenceMultiplier(), 4);
- s.setOccurrence(MyMoneySchedule::OCCUR_TWICEYEARLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_TWICEYEARLY);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_MONTHLY);
+ s.setOccurrence(Schedule::Occurrence::TwiceYearly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::TwiceYearly);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly);
QCOMPARE(s.occurrenceMultiplier(), 6);
- s.setOccurrence(MyMoneySchedule::OCCUR_YEARLY);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_YEARLY);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_YEARLY);
+ s.setOccurrence(Schedule::Occurrence::Yearly);
+ QCOMPARE(s.occurrence(), Schedule::Occurrence::Yearly);
+ QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly);
QCOMPARE(s.occurrenceMultiplier(), 1);
- s.setOccurrence(MyMoneySchedule::OCCUR_EVERYOTHERYEAR);
- QCOMPARE(s.occurrence(), MyMoneySchedule::OCCUR_EVERYOTHERYEAR);
- QCOMPARE(s.occurrencePeriod(), MyMoneySchedule::OCCUR_YEARLY);
+ 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
- MyMoneySchedule::occurrenceE occ;
+ Schedule::Occurrence occ;
int mult;
- occ = MyMoneySchedule::OCCUR_ONCE; mult = 1;
+ occ = Schedule::Occurrence::Once; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_ONCE && mult == 1);
- occ = MyMoneySchedule::OCCUR_DAILY; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Once && mult == 1);
+ occ = Schedule::Occurrence::Daily; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_DAILY && mult == 1);
- occ = MyMoneySchedule::OCCUR_WEEKLY; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Daily && mult == 1);
+ occ = Schedule::Occurrence::Weekly; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 1);
- occ = MyMoneySchedule::OCCUR_EVERYOTHERWEEK; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 1);
+ occ = Schedule::Occurrence::EveryOtherWeek; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 2);
- occ = MyMoneySchedule::OCCUR_FORTNIGHTLY; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 2);
+ occ = Schedule::Occurrence::Fortnightly; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 2);
- occ = MyMoneySchedule::OCCUR_EVERYHALFMONTH; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 2);
+ occ = Schedule::Occurrence::EveryHalfMonth; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_EVERYHALFMONTH && mult == 1);
- occ = MyMoneySchedule::OCCUR_EVERYTHREEWEEKS; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::EveryHalfMonth && mult == 1);
+ occ = Schedule::Occurrence::EveryThreeWeeks; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 3);
- occ = MyMoneySchedule::OCCUR_EVERYFOURWEEKS; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 3);
+ occ = Schedule::Occurrence::EveryFourWeeks; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 4);
- occ = MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 4);
+ occ = Schedule::Occurrence::EveryThirtyDays; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_DAILY && mult == 30);
- occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Daily && mult == 30);
+ occ = Schedule::Occurrence::Monthly; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 1);
- occ = MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 1);
+ occ = Schedule::Occurrence::EveryEightWeeks; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 8);
- occ = MyMoneySchedule::OCCUR_EVERYOTHERMONTH; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 8);
+ occ = Schedule::Occurrence::EveryOtherMonth; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 2);
- occ = MyMoneySchedule::OCCUR_EVERYTHREEMONTHS; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 2);
+ occ = Schedule::Occurrence::EveryThreeMonths; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 3);
- occ = MyMoneySchedule::OCCUR_QUARTERLY; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 3);
+ occ = Schedule::Occurrence::Quarterly; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 3);
- occ = MyMoneySchedule::OCCUR_EVERYFOURMONTHS; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 3);
+ occ = Schedule::Occurrence::EveryFourMonths; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 4);
- occ = MyMoneySchedule::OCCUR_TWICEYEARLY; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 4);
+ occ = Schedule::Occurrence::TwiceYearly; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 6);
- occ = MyMoneySchedule::OCCUR_YEARLY; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 6);
+ occ = Schedule::Occurrence::Yearly; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_YEARLY && mult == 1);
- occ = MyMoneySchedule::OCCUR_EVERYOTHERYEAR; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Yearly && mult == 1);
+ occ = Schedule::Occurrence::EveryOtherYear; mult = 1;
MyMoneySchedule::simpleToCompoundOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_YEARLY && mult == 2);
+ QVERIFY(occ == Schedule::Occurrence::Yearly && mult == 2);
// Compound to Simple Occurrences
- occ = MyMoneySchedule::OCCUR_ONCE; mult = 1;
+ occ = Schedule::Occurrence::Once; mult = 1;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_ONCE && mult == 1);
- occ = MyMoneySchedule::OCCUR_DAILY; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Once && mult == 1);
+ occ = Schedule::Occurrence::Daily; mult = 1;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_DAILY && mult == 1);
- occ = MyMoneySchedule::OCCUR_WEEKLY; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::Daily && mult == 1);
+ occ = Schedule::Occurrence::Weekly; mult = 1;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_WEEKLY && mult == 1);
- occ = MyMoneySchedule::OCCUR_WEEKLY; mult = 2;
+ QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 1);
+ occ = Schedule::Occurrence::Weekly; mult = 2;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_EVERYOTHERWEEK && mult == 1);
- // MyMoneySchedule::OCCUR_FORTNIGHTLY not converted back
- occ = MyMoneySchedule::OCCUR_EVERYHALFMONTH; mult = 1;
+ 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 == MyMoneySchedule::OCCUR_EVERYHALFMONTH && mult == 1);
- occ = MyMoneySchedule::OCCUR_WEEKLY; mult = 3;
+ QVERIFY(occ == Schedule::Occurrence::EveryHalfMonth && mult == 1);
+ occ = Schedule::Occurrence::Weekly; mult = 3;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_EVERYTHREEWEEKS && mult == 1);
- occ = MyMoneySchedule::OCCUR_WEEKLY ; mult = 4;
+ QVERIFY(occ == Schedule::Occurrence::EveryThreeWeeks && mult == 1);
+ occ = Schedule::Occurrence::Weekly ; mult = 4;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_EVERYFOURWEEKS && mult == 1);
- occ = MyMoneySchedule::OCCUR_DAILY; mult = 30;
+ QVERIFY(occ == Schedule::Occurrence::EveryFourWeeks && mult == 1);
+ occ = Schedule::Occurrence::Daily; mult = 30;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS && mult == 1);
- occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::EveryThirtyDays && mult == 1);
+ occ = Schedule::Occurrence::Monthly; mult = 1;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_MONTHLY && mult == 1);
- occ = MyMoneySchedule::OCCUR_WEEKLY; mult = 8;
+ QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 1);
+ occ = Schedule::Occurrence::Weekly; mult = 8;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS && mult == 1);
- occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 2;
+ QVERIFY(occ == Schedule::Occurrence::EveryEightWeeks && mult == 1);
+ occ = Schedule::Occurrence::Monthly; mult = 2;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_EVERYOTHERMONTH && mult == 1);
- occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 3;
+ QVERIFY(occ == Schedule::Occurrence::EveryOtherMonth && mult == 1);
+ occ = Schedule::Occurrence::Monthly; mult = 3;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_EVERYTHREEMONTHS && mult == 1);
- // MyMoneySchedule::OCCUR_QUARTERLY not converted back
- occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 4;
+ 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 == MyMoneySchedule::OCCUR_EVERYFOURMONTHS && mult == 1);
- occ = MyMoneySchedule::OCCUR_MONTHLY; mult = 6;
+ QVERIFY(occ == Schedule::Occurrence::EveryFourMonths && mult == 1);
+ occ = Schedule::Occurrence::Monthly; mult = 6;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_TWICEYEARLY && mult == 1);
- occ = MyMoneySchedule::OCCUR_YEARLY; mult = 1;
+ QVERIFY(occ == Schedule::Occurrence::TwiceYearly && mult == 1);
+ occ = Schedule::Occurrence::Yearly; mult = 1;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_YEARLY && mult == 1);
- occ = MyMoneySchedule::OCCUR_YEARLY; mult = 2;
+ QVERIFY(occ == Schedule::Occurrence::Yearly && mult == 1);
+ occ = Schedule::Occurrence::Yearly; mult = 2;
MyMoneySchedule::compoundToSimpleOccurrence(mult, occ);
- QVERIFY(occ == MyMoneySchedule::OCCUR_EVERYOTHERYEAR && mult == 1);
+ 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() == 0);
// This should be a processing day.
QVERIFY(s.isProcessingDate(QDate(2009, 12, 31)));
// This should be a processing day when there is no calendar.
QVERIFY(s.isProcessingDate(QDate(2010, 1, 1)));
// This should be a non-processing day as it is on a weekend.
QVERIFY(!s.isProcessingDate(QDate(2010, 1, 2)));
}
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(
"<!DOCTYPE TEST>\n"
"<SCHEDULE-CONTAINER>\n"
" <SCHEDULED_TX startDate=\"%1\" autoEnter=\"0\" weekendOption=\"1\" lastPayment=\"%2\" paymentType=\"2\" endDate=\"%3\" type=\"4\" id=\"SCH0042\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"32\" >\n" // krazy:exclude=spelling
" <PAYMENTS/>\n"
" <TRANSACTION postdate=\"\" memo=\"\" id=\"\" commodity=\"GBP\" entrydate=\"\" >\n"
" <SPLITS>\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"Transfer\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" id=\"S0001\" account=\"A000076\" />\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"Transfer\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"-96379/100\" id=\"S0002\" account=\"A000276\" />\n"
" </SPLITS>\n"
" </TRANSACTION>\n"
" </SCHEDULED_TX>\n"
"</SCHEDULE-CONTAINER>\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);
QVERIFY(sch.isFinished() == true);
- QVERIFY(sch.occurrencePeriod() == MyMoneySchedule::OCCUR_MONTHLY);
+ QVERIFY(sch.occurrencePeriod() == Schedule::Occurrence::Monthly);
QVERIFY(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(MyMoneySchedule::OCCUR_MONTHLY);
- s.setWeekendOption(MyMoneySchedule::MoveBefore);
+ 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
//QVERIFY(s.adjustedNextPayment(adjustedDueDate) == adjustedDueDate);
//this is the expected behaviour
QVERIFY(s.adjustedNextPayment(s.adjustedNextDueDate()) == s.adjustedDate(nextDueDate, s.weekendOption()));
}
void MyMoneyScheduleTest::testReplaceId()
{
MyMoneySchedule sch;
QDate paymentInFuture = QDate::currentDate().addDays(7);
QString ref_ok = QString(
"<!DOCTYPE TEST>\n"
"<SCHEDULE-CONTAINER>\n"
" <SCHEDULED_TX startDate=\"%1\" autoEnter=\"0\" weekendOption=\"1\" lastPayment=\"%2\" paymentType=\"2\" endDate=\"%3\" type=\"4\" id=\"SCH0042\" name=\"A Name\" fixed=\"1\" occurenceMultiplier=\"1\" occurence=\"32\" >\n" // krazy:exclude=spelling
" <PAYMENTS/>\n"
" <TRANSACTION postdate=\"\" memo=\"\" id=\"\" commodity=\"GBP\" entrydate=\"\" >\n"
" <SPLITS>\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"96379/100\" action=\"Transfer\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"96379/100\" id=\"S0001\" account=\"A000076\" />\n"
" <SPLIT payee=\"P000001\" reconciledate=\"\" shares=\"-96379/100\" action=\"Transfer\" number=\"\" reconcileflag=\"2\" memo=\"\" value=\"-96379/100\" id=\"S0002\" account=\"A000276\" />\n"
" </SPLITS>\n"
" </TRANSACTION>\n"
" </SCHEDULED_TX>\n"
"</SCHEDULE-CONTAINER>\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);
QVERIFY(sch.transaction().postDate().isValid() == false);
QVERIFY(sch.transaction().splits()[0].accountId() == "A000076");
QVERIFY(sch.transaction().splits()[1].accountId() == "A000276");
QVERIFY(sch.replaceId("A000079", "A000076") == true);
QVERIFY(sch.transaction().splits()[0].accountId() == "A000079");
QVERIFY(sch.transaction().splits()[1].accountId() == "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(MyMoneySchedule::OCCUR_MONTHLY);
+ 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(MyMoneySchedule::MoveAfter);
+ s.setWeekendOption(Schedule::WeekendOption::MoveAfter);
s.setNextDueDate(endDate);
// the payment should be found between the respective date and one month after
QVERIFY(s.paymentDates(endDate, endDate.addMonths(1)).count() == 1);
// the next payment must be the final one
QVERIFY(s.nextPayment(refDate) == endDate);
// and since it is on a Saturday, the adjusted one must be on the
// following Monday
QVERIFY(s.adjustedNextPayment(refDate) == endDate.addDays(2));
// reference for Sunday is still OK
QVERIFY(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));
QVERIFY(s.transactionsRemaining() == 2);
}
void MyMoneyScheduleTest::testElementNames()
{
QMetaEnum e = QMetaEnum::fromType<MyMoneySchedule::elNameE>();
for (int i = 0; i < e.keyCount(); ++i) {
bool isEmpty = MyMoneySchedule::getElName(static_cast<MyMoneySchedule::elNameE>(e.value(i))).isEmpty();
if (isEmpty)
qWarning() << "Empty element's name" << e.key(i);
QVERIFY(!isEmpty);
}
}
void MyMoneyScheduleTest::testAttributeNames()
{
QMetaEnum e = QMetaEnum::fromType<MyMoneySchedule::attrNameE>();
for (int i = 0; i < e.keyCount(); ++i) {
bool isEmpty = MyMoneySchedule::getAttrName(static_cast<MyMoneySchedule::attrNameE>(e.value(i))).isEmpty();
if (isEmpty)
qWarning() << "Empty attribute's name" << e.key(i);
QVERIFY(!isEmpty);
}
}
diff --git a/kmymoney/mymoney/tests/onlinejobadministration-test.cpp b/kmymoney/mymoney/tests/onlinejobadministration-test.cpp
index d9b8befd8..93af3579c 100644
--- a/kmymoney/mymoney/tests/onlinejobadministration-test.cpp
+++ b/kmymoney/mymoney/tests/onlinejobadministration-test.cpp
@@ -1,72 +1,72 @@
/*
* This file is part of KMyMoney, A Personal Finance Manager by KDE
* Copyright (C) 2014 Christian Dávid <christian-david@web.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "onlinejobadministration-test.h"
#include <QtTest/QTest>
#include "onlinejobadministration.h"
#include "mymoney/mymoneyfile.h"
#include "mymoney/storage/mymoneyseqaccessmgr.h"
#include "onlinetasks/dummy/tasks/dummytask.h"
QTEST_GUILESS_MAIN(onlineJobAdministrationTest)
void onlineJobAdministrationTest::initTestCase()
{
file = MyMoneyFile::instance();
storage = new MyMoneySeqAccessMgr;
file->attachStorage(storage);
try {
MyMoneyAccount account = MyMoneyAccount();
account.setName("Test Account");
- account.setAccountType(MyMoneyAccount::Savings);
+ account.setAccountType(eMyMoney::Account::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/plugins/csvexport/csvexportdlg.cpp b/kmymoney/plugins/csvexport/csvexportdlg.cpp
index bdcba13fa..090990e5b 100644
--- a/kmymoney/plugins/csvexport/csvexportdlg.cpp
+++ b/kmymoney/plugins/csvexport/csvexportdlg.cpp
@@ -1,256 +1,259 @@
/***************************************************************************
csvexportdlg.cpp - description
-------------------
begin : Wed Mar 20 2013
copyright : (C) 2013-03-20 by Allan Anderson
email : Allan Anderson agander93@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 "csvexportdlg.h"
#include "ui_csvexportdlg.h"
// ----------------------------------------------------------------------------
// QT Headers
#include <QList>
#include <QProgressBar>
#include <QPushButton>
#include <QStandardPaths>
#include <QFileDialog>
// ----------------------------------------------------------------------------
// KDE Headers
#include <KMessageBox>
#include <KGuiItem>
#include <KStandardGuiItem>
#include <KConfigGroup>
#include <KSharedConfig>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Headers
#include "mymoneyfile.h"
+#include "mymoneyaccount.h"
+#include "mymoneytransaction.h"
+#include "mymoneytransactionfilter.h"
#include "icons/icons.h"
using namespace Icons;
CsvExportDlg::CsvExportDlg(QWidget *parent) : QDialog(parent), ui(new Ui::CsvExportDlg)
{
ui->setupUi(this);
m_fieldDelimiterCharList << "," << ";" << "\t";
ui->m_separatorComboBox->setCurrentIndex(-1);
// Set (almost) all the last used options
readConfig();
loadAccounts();
// load button icons
KGuiItem::assign(ui->m_qbuttonCancel, KStandardGuiItem::cancel());
KGuiItem okButtonItem(i18n("&Export"),
QIcon::fromTheme(g_Icons[Icon::DocumentExport]),
i18n("Start operation"),
i18n("Use this to start the export operation"));
KGuiItem::assign(ui->m_qbuttonOk, okButtonItem);
KGuiItem browseButtonItem(i18n("&Browse..."),
QIcon::fromTheme(g_Icons[Icon::DocumentOpen]),
i18n("Select filename"),
i18n("Use this to select a filename to export to"));
KGuiItem::assign(ui->m_qbuttonBrowse, browseButtonItem);
// connect the buttons to their functionality
connect(ui->m_qbuttonBrowse, SIGNAL(clicked()), this, SLOT(slotBrowse()));
connect(ui->m_qbuttonOk, SIGNAL(clicked()), this, SLOT(slotOkClicked()));
connect(ui->m_qbuttonCancel, SIGNAL(clicked()), this, SLOT(reject()));
// connect the change signals to the check slot and perform initial check
connect(ui->m_qlineeditFile, SIGNAL(editingFinished()), this, SLOT(checkData()));
connect(ui->m_radioButtonAccount, SIGNAL(toggled(bool)), this, SLOT(checkData()));
connect(ui->m_radioButtonCategories, SIGNAL(toggled(bool)), this, SLOT(checkData()));
connect(ui->m_accountComboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(checkData(QString)));
connect(ui->m_separatorComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(separator(int)));
connect(ui->m_separatorComboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(checkData()));
checkData(QString());
}
CsvExportDlg::~CsvExportDlg()
{
}
void CsvExportDlg::slotBrowse()
{
QString newName(QFileDialog::getSaveFileName(this, QString(), QString(), QLatin1String("*.CSV")));
if (newName.indexOf('.') == -1)
newName += QLatin1String(".csv");
if (!newName.isEmpty())
ui->m_qlineeditFile->setText(newName);
}
void CsvExportDlg::slotOkClicked()
{
// Make sure we save the last used settings for use next time,
writeConfig();
accept();
}
void CsvExportDlg::separator(int separatorIndex)
{
m_separator = m_fieldDelimiterCharList[separatorIndex];
}
void CsvExportDlg::readConfig()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig(QStandardPaths::locate(QStandardPaths::ConfigLocation, QLatin1String("csvexporterrc")));
KConfigGroup conf = config->group("Last Use Settings");
ui->m_qlineeditFile->setText(conf.readEntry("CsvExportDlg_LastFile"));
ui->m_radioButtonAccount->setChecked(conf.readEntry("CsvExportDlg_AccountOpt", true));
ui->m_radioButtonCategories->setChecked(conf.readEntry("CsvExportDlg_CatOpt", true));
ui->m_kmymoneydateStart->setDate(conf.readEntry("CsvExportDlg_StartDate", QDate()));
ui->m_kmymoneydateEnd->setDate(conf.readEntry("CsvExportDlg_EndDate", QDate()));
}
void CsvExportDlg::writeConfig()
{
KSharedConfigPtr config = KSharedConfig::openConfig(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1String("/csvexporterrc"));
KConfigGroup grp = config->group("Last Use Settings");
grp.writeEntry("CsvExportDlg_LastFile", ui->m_qlineeditFile->text());
grp.writeEntry("CsvExportDlg_AccountOpt", ui->m_radioButtonAccount->isChecked());
grp.writeEntry("CsvExportDlg_CatOpt", ui->m_radioButtonCategories->isChecked());
grp.writeEntry("CsvExportDlg_StartDate", QDateTime(ui->m_kmymoneydateStart->date()));
grp.writeEntry("CsvExportDlg_EndDate", QDateTime(ui->m_kmymoneydateEnd->date()));
grp.writeEntry("CsvExportDlg_separatorIndex", ui->m_separatorComboBox->currentIndex());
config->sync();
}
void CsvExportDlg::checkData(const QString& accountName)
{
bool okEnabled = false;
if (!ui->m_qlineeditFile->text().isEmpty()) {
QString strFile(ui->m_qlineeditFile->text());
int dot = strFile.indexOf('.');
if (dot != -1) {
strFile.chop(strFile.length() - dot);
}
strFile += QLatin1String(".csv");
ui->m_qlineeditFile->setText(strFile);
}
QDate earliestDate(QDate(2500, 01, 01));
QDate latestDate(QDate(1900, 01, 01));
QList<MyMoneyTransaction> listTrans;
QList<MyMoneyTransaction>::Iterator itTrans;
MyMoneyAccount account;
MyMoneyFile* file = MyMoneyFile::instance();
if (!accountName.isEmpty()) {
account = file->accountByName(accountName);
m_accountId = account.id();
MyMoneyAccount accnt;
- if (account.accountType() == MyMoneyAccount::Investment) {
+ if (account.accountType() == eMyMoney::Account::Investment) {
// If this is Investment account, we need child account.
QStringList listAccounts = account.accountList();
QStringList::Iterator itAccounts;
for (itAccounts = listAccounts.begin(); itAccounts != listAccounts.end(); ++itAccounts) {
accnt = file->account((*itAccounts));
MyMoneyTransactionFilter filter(accnt.id());
listTrans = file->transactionList(filter);
if (!listTrans.isEmpty()) {
if (listTrans[0].postDate() < earliestDate) {
earliestDate = listTrans[0].postDate();
}
latestDate = listTrans[listTrans.count() - 1].postDate();
}
}
} else { //Checking, etc.
MyMoneyTransactionFilter filter(account.id());
listTrans = file->transactionList(filter);
if (listTrans.isEmpty()) {
KMessageBox::sorry(0, i18n("There are no entries in this account.\n"),
i18n("Invalid account"));
return;
}
earliestDate = listTrans[0].postDate();
latestDate = listTrans[listTrans.count() - 1].postDate();
}
ui->m_kmymoneydateStart->setDate(earliestDate);
ui->m_kmymoneydateEnd->setDate(latestDate);
ui->m_accountComboBox->setCompletedText(accnt.id());
}
if (!ui->m_qlineeditFile->text().isEmpty()
&& !ui->m_accountComboBox->currentText().isEmpty()
&& ui->m_kmymoneydateStart->date() <= ui->m_kmymoneydateEnd->date()
&& (ui->m_radioButtonAccount->isChecked() || ui->m_radioButtonCategories->isChecked())
&& (ui->m_separatorComboBox->currentIndex() >= 0)) {
okEnabled = true;
}
ui->m_qbuttonOk->setEnabled(okEnabled);
}
void CsvExportDlg::loadAccounts()
{
QStringList lst = getAccounts();
for (int i = 0; i < lst.count(); i++) {
ui->m_accountComboBox->addItem(lst[i]);
}
ui->m_accountComboBox->setCurrentIndex(-1);
}
QStringList CsvExportDlg::getAccounts()
{
QStringList list;
MyMoneyFile* file = MyMoneyFile::instance();
QString accountId;
// Get a list of all accounts
QList<MyMoneyAccount> accounts;
file->accountList(accounts);
QList<MyMoneyAccount>::const_iterator it_account = accounts.constBegin();
m_idList.clear();
while (it_account != accounts.constEnd()) {
MyMoneyAccount account((*it_account).id(), (*it_account));
if (!account.isClosed()) {
- MyMoneyAccount::accountTypeE accntType = account.accountType();
- MyMoneyAccount::accountTypeE accntGroup = account.accountGroup();
- if ((accntGroup == MyMoneyAccount::Liability) || ((accntGroup == MyMoneyAccount::Asset) && (accntType != MyMoneyAccount::Stock))) { // ie Asset or Liability types
+ eMyMoney::Account accntType = account.accountType();
+ eMyMoney::Account accntGroup = account.accountGroup();
+ if ((accntGroup == eMyMoney::Account::Liability) || ((accntGroup == eMyMoney::Account::Asset) && (accntType != eMyMoney::Account::Stock))) { // ie Asset or Liability types
list << account.name();
m_idList << account.id();
}
}
++it_account;
}
qSort(list.begin(), list.end(), caseInsensitiveLessThan);
return list;
}
void CsvExportDlg::slotStatusProgressBar(int current, int total)
{
if (total == -1 && current == -1) { // reset
ui->progressBar->setValue(ui->progressBar->maximum());
} else if (total != 0) { // init
ui->progressBar->setMaximum(total);
ui->progressBar->setValue(0);
ui->progressBar->show();
} else { // update
ui->progressBar->setValue(current);
}
update();
}
bool caseInsensitiveLessThan(const QString &s1, const QString &s2)
{
return s1.toLower() < s2.toLower();
}
diff --git a/kmymoney/plugins/csvexport/csvwriter.cpp b/kmymoney/plugins/csvexport/csvwriter.cpp
index 0f301d001..c2907e3f1 100644
--- a/kmymoney/plugins/csvexport/csvwriter.cpp
+++ b/kmymoney/plugins/csvexport/csvwriter.cpp
@@ -1,457 +1,461 @@
/***************************************************************************
csvwriter.cpp - description
--------------------
begin : Wed Mar 20 2013
copyright : (C) 2013-03-20 by Allan Anderson
email : Allan Anderson agander93@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 "csvwriter.h"
// ----------------------------------------------------------------------------
// QT Headers
#include <QFile>
#include <QList>
#include <QDebug>
#include <QStringBuilder>
// ----------------------------------------------------------------------------
// KDE Headers
#include <KMessageBox>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Headers
#include "mymoneyfile.h"
+#include "mymoneytransaction.h"
+#include "mymoneytransactionfilter.h"
+#include "mymoneysplit.h"
+#include "mymoneypayee.h"
#include "csvexportdlg.h"
#include "csvexporterplugin.h"
CsvWriter::CsvWriter() :
m_plugin(0),
m_firstSplit(false),
m_highestSplitCount(0),
m_noError(true)
{
}
CsvWriter::~CsvWriter()
{
}
void CsvWriter::write(const QString& filename,
const QString& accountId, const bool accountData,
const bool categoryData,
const QDate& startDate, const QDate& endDate,
const QString& separator)
{
m_separator = separator;
QFile csvFile(filename);
if (csvFile.open(QIODevice::WriteOnly)) {
QTextStream s(&csvFile);
s.setCodec("UTF-8");
m_plugin->exporterDialog()->show();
try {
if (categoryData) {
writeCategoryEntries(s);
}
if (accountData) {
writeAccountEntry(s, accountId, startDate, endDate);
}
emit signalProgress(-1, -1);
} catch (const MyMoneyException &e) {
QString errMsg = i18n("Unexpected exception '%1' thrown in %2, line %3 "
"caught in MyMoneyCsvWriter::write()", e.what(), e.file(), e.line());
KMessageBox::error(0, errMsg);
}
csvFile.close();
qDebug() << i18n("Export completed.\n");
delete m_plugin->exporterDialog(); // Can now delete as export finished
} else {
KMessageBox::error(0, i18n("Unable to open file '%1' for writing", filename));
}
}
void CsvWriter::writeAccountEntry(QTextStream& stream, const QString& accountId, const QDate& startDate, const QDate& endDate)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount account;
QString data;
account = file->account(accountId);
MyMoneyTransactionFilter filter(accountId);
QString type = account.accountTypeToString(account.accountType());
data = QString(i18n("Account Type:"));
if (type == QLatin1String("Investment")) {
data += QString("%1\n\n").arg(type);
m_headerLine << QString(i18n("Date")) << QString(i18n("Security")) << QString(i18n("Action/Type")) << QString(i18n("Amount")) << QString(i18n("Quantity")) << QString(i18n("Price")) << QString(i18n("Interest")) << QString(i18n("Fees")) << QString(i18n("Account")) << QString(i18n("Memo")) << QString(i18n("Status"));
data += m_headerLine.join(m_separator);
extractInvestmentEntries(accountId, startDate, endDate);
} else {
data += QString("%1\n\n").arg(type);
m_headerLine << QString(i18n("Date")) << QString(i18n("Payee")) << QString(i18n("Amount")) << QString(i18n("Account/Cat")) << QString(i18n("Memo")) << QString(i18n("Status")) << QString(i18n("Number"));
filter.setDateFilter(startDate, endDate);
QList<MyMoneyTransaction> trList = file->transactionList(filter);
QList<MyMoneyTransaction>::ConstIterator it;
signalProgress(0, trList.count());
int count = 0;
m_highestSplitCount = 0;
for (it = trList.constBegin(); it != trList.constEnd(); ++it) {
writeTransactionEntry(*it, accountId, ++count);
if (m_noError)
signalProgress(count, 0);
}
data += m_headerLine.join(m_separator);
}
QString result;
QMap<QString, QString>::const_iterator it_map = m_map.constBegin();
while (it_map != m_map.constEnd()) {
result += it_map.value();
++it_map;
}
stream << data << result << QLatin1Char('\n');
}
void CsvWriter::writeCategoryEntries(QTextStream &s)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount income;
MyMoneyAccount expense;
income = file->income();
expense = file->expense();
QStringList list = income.accountList() + expense.accountList();
emit signalProgress(0, list.count());
QStringList::Iterator it_catList;
int count = 0;
for (it_catList = list.begin(); it_catList != list.end(); ++it_catList) {
writeCategoryEntry(s, *it_catList, "");
emit signalProgress(++count, 0);
}
}
void CsvWriter::writeCategoryEntry(QTextStream &s, const QString& accountId, const QString& leadIn)
{
MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId);
QString name = acc.name();
s << leadIn << name << m_separator;
- s << (acc.accountGroup() == MyMoneyAccount::Expense ? QLatin1Char('E') : QLatin1Char('I'));
+ s << (acc.accountGroup() == eMyMoney::Account::Expense ? QLatin1Char('E') : QLatin1Char('I'));
s << endl;
QStringList list = acc.accountList();
QStringList::Iterator it_catList;
name += m_separator;
for (it_catList = list.begin(); it_catList != list.end(); ++it_catList) {
writeCategoryEntry(s, *it_catList, name);
}
}
void CsvWriter::writeTransactionEntry(const MyMoneyTransaction& t, const QString& accountId, const int count)
{
m_firstSplit = true;
m_noError = true;
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneySplit split = t.splitByAccount(accountId);
QList<MyMoneySplit> splits = t.splits();
if (splits.count() < 2) {
KMessageBox::sorry(0, i18n("Transaction number '%1' is missing an account assignment.\n"
"Date '%2', Payee '%3'.\nTransaction dropped.\n", count, t.postDate().toString(Qt::ISODate), file->payee(split.payeeId()).name()),
i18n("Invalid transaction"));
m_noError = false;
return;
}
QString str;
str += QLatin1Char('\n');
str += QString("%1" + m_separator).arg(t.postDate().toString(Qt::ISODate));
MyMoneyPayee payee = file->payee(split.payeeId());
str += QString("%1" + m_separator).arg(payee.name());
QString txt = split.value().formatMoney("", 2, false);
str += QString("%1" + m_separator).arg(txt);
if (splits.count() > 1) {
MyMoneySplit sp = t.splitByAccount(accountId, false);
QString tmp = QString("%1").arg(file->accountToCategory(sp.accountId()));
str += tmp + m_separator;
}
QString memo = split.memo();
memo.replace('\n', '~').remove('\'');
QString localeThousands = QLocale().groupSeparator(); // In case of clash with field separator
if (m_separator == localeThousands) {
memo.replace(localeThousands, QString());
}
str += QString("%1" + m_separator).arg(memo);
switch (split.reconcileFlag()) {
case MyMoneySplit::Cleared:
str += QLatin1String("C") + m_separator;
break;
case MyMoneySplit::Reconciled:
case MyMoneySplit::Frozen:
str += QLatin1String("R") + m_separator;
break;
default:
str += m_separator;
break;
}
str += split.number();
if (splits.count() > 2) {
QList<MyMoneySplit>::ConstIterator it;
for (it = splits.constBegin(); it != splits.constEnd(); ++it) {
if (!((*it) == split)) {
writeSplitEntry(str, *it, splits.count() - 1);
}
}
}
QString date = t.postDate().toString(Qt::ISODate);
m_map.insertMulti(date, str);
}
void CsvWriter::writeSplitEntry(QString &str, const MyMoneySplit& split, const int splitCount)
{
if (m_firstSplit) {
m_firstSplit = false;
str += m_separator;
}
MyMoneyFile* file = MyMoneyFile::instance();
QString splt = QString("%1").arg(file->accountToCategory(split.accountId()));
str += splt + m_separator;
if (splitCount > m_highestSplitCount) {
m_highestSplitCount++;
m_headerLine << QString(i18n("splitCategory")) << QString(i18n("splitMemo")) << QString(i18n("splitAmount"));
m_headerLine.join(m_separator);
}
QString m = split.memo();
m.replace(QLatin1Char('\n'), QLatin1Char('~'));
QString localeThousands = QLocale().groupSeparator(); // In case of clash with field separator
if (m_separator == localeThousands) {
m.replace(localeThousands, QString());
}
str += QString("%1" + m_separator).arg(m);
QString txt = QString("%1" + m_separator).arg(split.value().formatMoney("", 2, false));
str += txt;
}
void CsvWriter::extractInvestmentEntries(const QString& accountId, const QDate& startDate, const QDate& endDate)
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<QString> accList = file->account(accountId).accountList();
QList<QString>::ConstIterator itAcc;
for (itAcc = accList.constBegin(); itAcc != accList.constEnd(); ++itAcc) {
MyMoneyTransactionFilter filter((*itAcc));
filter.setDateFilter(startDate, endDate);
QList<MyMoneyTransaction> list = file->transactionList(filter);
QList<MyMoneyTransaction>::ConstIterator itList;
signalProgress(0, list.count());
int count = 0;
for (itList = list.constBegin(); itList != list.constEnd(); ++itList) {
writeInvestmentEntry(*itList, ++count);
signalProgress(count, 0);
}
}
}
void CsvWriter::writeInvestmentEntry(const MyMoneyTransaction& t, const int count)
{
QString strQuantity;
QString strAmount;
QString strPrice;
QString strAccName;
QString strCheckingAccountName;
QString strMemo;
QString strAction;
QString strStatus;
QString strInterest;
QString strFees;
MyMoneyFile* file = MyMoneyFile::instance();
QString chkAccnt;
QList<MyMoneySplit> lst = t.splits();
QList<MyMoneySplit>::Iterator itSplit;
- MyMoneyAccount::_accountTypeE typ;
+ eMyMoney::Account typ;
QString chkAccntId;
MyMoneyMoney qty;
MyMoneyMoney value;
- QMap<MyMoneyAccount::_accountTypeE, QString> map;
+ QMap<eMyMoney::Account, QString> map;
for (int i = 0; i < lst.count(); i++) {
MyMoneyAccount acc = file->account(lst[i].accountId());
QString accName = acc.name();
typ = acc.accountType();
map.insert(typ, lst[i].accountId());
- if (typ == MyMoneyAccount::Stock) {
+ if (typ == eMyMoney::Account::Stock) {
switch (lst[i].reconcileFlag()) {
case MyMoneySplit::Cleared:
strStatus = QLatin1Char('C');
break;
case MyMoneySplit::Reconciled:
case MyMoneySplit::Frozen:
strStatus = QLatin1Char('R');
break;
default:
strStatus.clear();
break;
}
strStatus += m_separator;
}
}
//
// Add date.
//
QString str = QString("\n%1" + m_separator).arg(t.postDate().toString(Qt::ISODate));
QString localeThousands = QLocale().groupSeparator(); // In case of clash with field separator
for (itSplit = lst.begin(); itSplit != lst.end(); ++itSplit) {
MyMoneyAccount acc = file->account((*itSplit).accountId());
//
- // MyMoneyAccount::Checkings.
+ // eMyMoney::Account::Checkings.
//
- if ((acc.accountType() == MyMoneyAccount::Checkings) || (acc.accountType() == MyMoneyAccount::Cash) || (acc.accountType() == MyMoneyAccount::Savings)) {
+ if ((acc.accountType() == eMyMoney::Account::Checkings) || (acc.accountType() == eMyMoney::Account::Cash) || (acc.accountType() == eMyMoney::Account::Savings)) {
chkAccntId = (*itSplit).accountId();
chkAccnt = file->account(chkAccntId).name();
strCheckingAccountName = file->accountToCategory(chkAccntId) + m_separator;
strAmount = (*itSplit).value().formatMoney("", 2).remove(localeThousands) + m_separator;
- } else if (acc.accountType() == MyMoneyAccount::Income) {
+ } else if (acc.accountType() == eMyMoney::Account::Income) {
//
- // MyMoneyAccount::Income.
+ // eMyMoney::Account::Income.
//
qty = (*itSplit).shares();
value = (*itSplit).value();
strInterest = value.formatMoney("", 2, false) + m_separator;
- } else if (acc.accountType() == MyMoneyAccount::Expense) {
+ } else if (acc.accountType() == eMyMoney::Account::Expense) {
//
- // MyMoneyAccount::Expense.
+ // eMyMoney::Account::Expense.
//
qty = (*itSplit).shares();
value = (*itSplit).value();
strFees = value.formatMoney("", 2, false) + m_separator;
- } else if (acc.accountType() == MyMoneyAccount::Stock) {
+ } else if (acc.accountType() == eMyMoney::Account::Stock) {
//
- // MyMoneyAccount::Stock.
+ // eMyMoney::Account::Stock.
//
strMemo = QString("%1" + m_separator).arg((*itSplit).memo());
strMemo.replace(QLatin1Char('\n'), QLatin1Char('~')).remove('\'');
//
// Actions.
//
if ((*itSplit).action() == QLatin1String("Dividend")) {
strAction = QLatin1String("DivX");
} else if ((*itSplit).action() == QLatin1String("IntIncome")) {
strAction = QLatin1String("IntIncX");
}
if ((strAction == QLatin1String("DivX")) || (strAction == QLatin1String("IntIncX"))) {
- if ((map.value(MyMoneyAccount::Checkings).isEmpty()) && (map.value(MyMoneyAccount::Cash).isEmpty())) {
+ if ((map.value(eMyMoney::Account::Checkings).isEmpty()) && (map.value(eMyMoney::Account::Cash).isEmpty())) {
KMessageBox::sorry(0, i18n("Transaction number '%1' is missing an account assignment.\n"
"Date '%2', Amount '%3'.\nTransaction dropped.\n", count, t.postDate().toString(Qt::ISODate), strAmount),
i18n("Invalid transaction"));
return;
}
} else if ((*itSplit).action() == QLatin1String("Buy")) {
qty = (*itSplit).shares();
if (qty.isNegative()) {
strAction = QLatin1String("Sell");
} else {
strAction = QLatin1String("Buy");
}
} else if ((*itSplit).action() == QLatin1String("Add")) {
qty = (*itSplit).shares();
if (qty.isNegative()) {
strAction = QLatin1String("Shrsout");
} else {
strAction = QLatin1String("Shrsin");
}
} else if ((*itSplit).action() == QLatin1String("Reinvest")) {
qty = (*itSplit).shares();
strAmount = (*itSplit).value().formatMoney("", 2).remove(localeThousands) + m_separator;
strAction = QLatin1String("ReinvDiv");
} else {
strAction = (*itSplit).action();
}
//
// Add action.
//
if ((strAction == QLatin1String("Buy")) || (strAction == QLatin1String("Sell")) || (strAction == QLatin1String("ReinvDiv"))) {
//
// Add total.
//
if (strAction == QLatin1String("Sell")) {
value = -value;
}
//
// Add price.
//
strPrice = (*itSplit).price().formatMoney("", 6, false);
if (!qty.isZero()) {
//
// Add quantity.
//
if (strAction == QLatin1String("Sell")) {
qty = -qty;
}
strQuantity = qty.formatMoney("", 2, false);
}
} else if ((strAction == QLatin1String("Shrsin")) || (strAction == QLatin1String("Shrsout"))) {
//
// Add quantity for "Shrsin" || "Shrsout".
//
if (strAction == QLatin1String("Shrsout")) {
qty = -qty;
}
strQuantity = qty.formatMoney("", 2, false);
}
strAccName = acc.name();
strAccName += m_separator;
strAction += m_separator;
strQuantity += m_separator;
strPrice += m_separator;
}
if (strCheckingAccountName.isEmpty()) {
strCheckingAccountName = m_separator;
}
if (strInterest.isEmpty()) {
strInterest = m_separator;
}
if (strFees.isEmpty()) {
strFees = m_separator;
}
} // end of itSplit loop
str += strAccName + strAction + strAmount + strQuantity + strPrice + strInterest + strFees + strCheckingAccountName + strMemo + strStatus;
QString date = t.postDate().toString(Qt::ISODate);
m_map.insertMulti(date, str);
}
diff --git a/kmymoney/plugins/csvimport/csvimporter.cpp b/kmymoney/plugins/csvimport/csvimporter.cpp
index 582170a4a..eaa652047 100644
--- a/kmymoney/plugins/csvimport/csvimporter.cpp
+++ b/kmymoney/plugins/csvimport/csvimporter.cpp
@@ -1,1758 +1,1760 @@
/***************************************************************************
csvimporter.cpp
-------------------
begin : Sun May 21 2017
copyright : (C) 2010 by Allan Anderson
email : agander93@gmail.com
copyright : (C) 2016-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 "csvimporter.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QTextCodec>
#include <QTextStream>
#include <QFileDialog>
#include <QRegularExpression>
#include <QStandardItem>
#include <QPointer>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KMessageBox>
#include <KConfigGroup>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
+#include "mymoneytransaction.h"
#include "csvutil.h"
#include "convdate.h"
const QHash<Profile, QString> CSVImporter::m_profileConfPrefix {
{Profile::Banking, QStringLiteral("Bank")},
{Profile::Investment, QStringLiteral("Invest")},
{Profile::CurrencyPrices, QStringLiteral("CPrices")},
{Profile::StockPrices, QStringLiteral("SPrices")}
};
const QHash<Column, QString> CSVImporter::m_colTypeConfName {
{Column::Date, QStringLiteral("DateCol")},
{Column::Memo, QStringLiteral("MemoCol")},
{Column::Number, QStringLiteral("NumberCol")},
{Column::Payee, QStringLiteral("PayeeCol")},
{Column::Amount, QStringLiteral("AmountCol")},
{Column::Credit, QStringLiteral("CreditCol")},
{Column::Debit, QStringLiteral("DebitCol")},
{Column::Category, QStringLiteral("CategoryCol")},
{Column::Type, QStringLiteral("TypeCol")},
{Column::Price, QStringLiteral("PriceCol")},
{Column::Quantity, QStringLiteral("QuantityCol")},
{Column::Fee, QStringLiteral("FeeCol")},
{Column::Symbol, QStringLiteral("SymbolCol")},
{Column::Name, QStringLiteral("NameCol")},
};
const QHash<miscSettingsE, QString> CSVImporter::m_miscSettingsConfName {
{ConfDirectory, QStringLiteral("Directory")},
{ConfEncoding, QStringLiteral("Encoding")},
{ConfDateFormat, QStringLiteral("DateFormat")},
{ConfFieldDelimiter, QStringLiteral("FieldDelimiter")},
{ConfTextDelimiter, QStringLiteral("TextDelimiter")},
{ConfDecimalSymbol, QStringLiteral("DecimalSymbol")},
{ConfStartLine, QStringLiteral("StartLine")},
{ConfTrailerLines, QStringLiteral("TrailerLines")},
{ConfOppositeSigns, QStringLiteral("OppositeSigns")},
{ConfFeeIsPercentage, QStringLiteral("FeeIsPercentage")},
{ConfFeeRate, QStringLiteral("FeeRate")},
{ConfMinFee, QStringLiteral("MinFee")},
{ConfSecurityName, QStringLiteral("SecurityName")},
{ConfSecuritySymbol, QStringLiteral("SecuritySymbol")},
{ConfCurrencySymbol, QStringLiteral("CurrencySymbol")},
{ConfPriceFraction, QStringLiteral("PriceFraction")},
{ConfDontAsk, QStringLiteral("DontAsk")},
{ConfHeight, QStringLiteral("Height")},
{ConfWidth, QStringLiteral("Width")}
};
const QHash<MyMoneyStatement::Transaction::EAction, QString> CSVImporter::m_transactionConfName {
{MyMoneyStatement::Transaction::eaBuy, QStringLiteral("BuyParam")},
{MyMoneyStatement::Transaction::eaSell, QStringLiteral("SellParam")},
{MyMoneyStatement::Transaction::eaReinvestDividend, QStringLiteral("ReinvdivParam")},
{MyMoneyStatement::Transaction::eaCashDividend, QStringLiteral("DivXParam")},
{MyMoneyStatement::Transaction::eaInterest, QStringLiteral("IntIncParam")},
{MyMoneyStatement::Transaction::eaShrsin, QStringLiteral("ShrsinParam")},
{MyMoneyStatement::Transaction::eaShrsout, QStringLiteral("ShrsoutParam")}
};
const QString CSVImporter::m_confProfileNames = QStringLiteral("ProfileNames");
const QString CSVImporter::m_confPriorName = QStringLiteral("Prior");
const QString CSVImporter::m_confMiscName = QStringLiteral("Misc");
CSVImporter::CSVImporter()
: m_profile(0)
{
m_convertDate = new ConvertDate;
m_file = new CSVFile;
m_priceFractions << MyMoneyMoney(0.01) << MyMoneyMoney(0.1) << MyMoneyMoney::ONE << MyMoneyMoney(10) << MyMoneyMoney(100);
validateConfigFile();
readMiscSettings();
}
CSVImporter::~CSVImporter()
{
delete m_convertDate;
delete m_file;
}
MyMoneyStatement CSVImporter::unattendedImport(const QString &filename, CSVProfile *profile)
{
MyMoneyStatement st;
m_profile = profile;
m_convertDate->setDateFormatIndex(m_profile->m_dateFormat);
if (m_file->getInFileName(filename)) {
m_file->readFile(m_profile);
m_file->setupParser(m_profile);
if (profile->m_decimalSymbol == DecimalSymbol::Auto) {
auto columns = getNumericalColumns();
if (detectDecimalSymbols(columns) != -2)
return st;
}
if (!createStatement(st))
st = MyMoneyStatement();
}
return st;
}
KSharedConfigPtr CSVImporter::configFile()
{
return KSharedConfig::openConfig(QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation))
.filePath(QStringLiteral("csvimporterrc")));
}
void CSVImporter::profileFactory(const Profile type, const QString &name)
{
// delete current profile
if (m_profile) {
delete m_profile;
m_profile = nullptr;
}
switch (type) {
default:
case Profile::Investment:
m_profile = new InvestmentProfile;
break;
case Profile::Banking:
m_profile = new BankingProfile;
break;
case Profile::CurrencyPrices:
case Profile::StockPrices:
m_profile = new PricesProfile(type);
break;
}
m_profile->m_profileName = name;
}
void CSVImporter::readMiscSettings() {
KConfigGroup miscGroup(configFile(), m_confMiscName);
m_autodetect.clear();
m_autodetect.insert(AutoFieldDelimiter, miscGroup.readEntry(QStringLiteral("AutoFieldDelimiter"), true));
m_autodetect.insert(AutoDecimalSymbol, miscGroup.readEntry(QStringLiteral("AutoDecimalSymbol"), true));
m_autodetect.insert(AutoDateFormat, miscGroup.readEntry(QStringLiteral("AutoDateFormat"), true));
m_autodetect.insert(AutoAccountInvest, miscGroup.readEntry(QStringLiteral("AutoAccountInvest"), true));
m_autodetect.insert(AutoAccountBank, miscGroup.readEntry(QStringLiteral("AutoAccountBank"), true));
}
void CSVImporter::validateConfigFile()
{
const KSharedConfigPtr config = configFile();
KConfigGroup profileNamesGroup(config, m_confProfileNames);
if (!profileNamesGroup.exists()) {
profileNamesGroup.writeEntry(m_profileConfPrefix.value(Profile::Banking), QStringList());
profileNamesGroup.writeEntry(m_profileConfPrefix.value(Profile::Investment), QStringList());
profileNamesGroup.writeEntry(m_profileConfPrefix.value(Profile::CurrencyPrices), QStringList());
profileNamesGroup.writeEntry(m_profileConfPrefix.value(Profile::StockPrices), QStringList());
profileNamesGroup.writeEntry(m_confPriorName + m_profileConfPrefix.value(Profile::Banking), int());
profileNamesGroup.writeEntry(m_confPriorName + m_profileConfPrefix.value(Profile::Investment), int());
profileNamesGroup.writeEntry(m_confPriorName + m_profileConfPrefix.value(Profile::CurrencyPrices), int());
profileNamesGroup.writeEntry(m_confPriorName + m_profileConfPrefix.value(Profile::StockPrices), int());
profileNamesGroup.sync();
}
KConfigGroup miscGroup(config, m_confMiscName);
if (!miscGroup.exists()) {
miscGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfHeight), "400");
miscGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfWidth), "800");
miscGroup.sync();
}
QList<int> confVer = miscGroup.readEntry("KMMVer", QList<int> {0, 0, 0});
if (updateConfigFile(confVer)) // write kmmVer only if there were no errors
miscGroup.writeEntry("KMMVer", confVer);
}
bool CSVImporter::updateConfigFile(QList<int> &confVer)
{
bool ret = true;
QList<int> kmmVer = QList<int> {5, 0, 0};
int kmmVersion = kmmVer.at(0) * 100 + kmmVer.at(1) * 10 + kmmVer.at(2);
int confVersion = confVer.at(0) * 100 + confVer.at(1) * 10 + confVer.at(2);
if (confVersion > kmmVersion) {
KMessageBox::information(0,
i18n("Version of your CSV config file is %1.%2.%3 and is newer than supported version %4.%5.%6. Expect troubles.",
confVer.at(0), confVer.at(1), confVer.at(2),
kmmVer.at(0), kmmVer.at(1), kmmVer.at(2)));
ret = false;
return ret;
} else if (confVersion == kmmVersion)
return true;
confVer = kmmVer;
const KSharedConfigPtr config = configFile();
QString configFilePath = config.constData()->name();
QFile::copy(configFilePath, configFilePath + QLatin1String(".bak"));
KConfigGroup profileNamesGroup(config, m_confProfileNames);
QStringList bankProfiles = profileNamesGroup.readEntry(m_profileConfPrefix.value(Profile::Banking), QStringList());
QStringList investProfiles = profileNamesGroup.readEntry(m_profileConfPrefix.value(Profile::Investment), QStringList());
QStringList invalidBankProfiles = profileNamesGroup.readEntry(QLatin1String("Invalid") + m_profileConfPrefix.value(Profile::Banking), QStringList()); // get profiles that was marked invalid during last update
QStringList invalidInvestProfiles = profileNamesGroup.readEntry(QLatin1String("Invalid") + m_profileConfPrefix.value(Profile::Investment), QStringList());
QString bankPrefix = m_profileConfPrefix.value(Profile::Banking) + QLatin1Char('-');
QString investPrefix = m_profileConfPrefix.value(Profile::Investment) + QLatin1Char('-');
// for kmm < 5.0.0 change 'BankNames' to 'ProfileNames' and remove 'MainWindow' group
if (confVersion < 500 && bankProfiles.isEmpty()) {
KConfigGroup oldProfileNamesGroup(config, "BankProfiles");
bankProfiles = oldProfileNamesGroup.readEntry("BankNames", QStringList()); // profile names are under 'BankNames' entry for kmm < 5.0.0
bankPrefix = QLatin1String("Profiles-"); // needed to remove non-existent profiles in first run
oldProfileNamesGroup.deleteGroup();
KConfigGroup oldMainWindowGroup(config, "MainWindow");
oldMainWindowGroup.deleteGroup();
KConfigGroup oldSecuritiesGroup(config, "Securities");
oldSecuritiesGroup.deleteGroup();
}
bool firstTry = false;
if (invalidBankProfiles.isEmpty() && invalidInvestProfiles.isEmpty()) // if there is no invalid profiles then this might be first update try
firstTry = true;
int invalidProfileResponse = QDialogButtonBox::No;
for (auto profileName = bankProfiles.begin(); profileName != bankProfiles.end();) {
KConfigGroup bankProfile(config, bankPrefix + *profileName);
if (!bankProfile.exists() && !invalidBankProfiles.contains(*profileName)) { // if there is reference to profile but no profile then remove this reference
profileName = bankProfiles.erase(profileName);
continue;
}
// for kmm < 5.0.0 remove 'FileType' and 'ProfileName' and assign them to either "Bank=" or "Invest="
if (confVersion < 500) {
QString lastUsedDirectory;
KConfigGroup oldBankProfile(config, QLatin1String("Profiles-") + *profileName); // if half of configuration is updated and the other one untouched this is needed
QString oldProfileType = oldBankProfile.readEntry("FileType", QString());
KConfigGroup newProfile;
if (oldProfileType == QLatin1String("Invest")) {
oldBankProfile.deleteEntry("BrokerageParam");
oldBankProfile.writeEntry(m_colTypeConfName.value(Column::Type), oldBankProfile.readEntry("PayeeCol"));
oldBankProfile.deleteEntry("PayeeCol");
oldBankProfile.deleteEntry("Filter");
oldBankProfile.deleteEntry("SecurityName");
lastUsedDirectory = oldBankProfile.readEntry("InvDirectory");
newProfile = KConfigGroup(config, m_profileConfPrefix.value(Profile::Investment) + QLatin1Char('-') + *profileName);
investProfiles.append(*profileName);
profileName = bankProfiles.erase(profileName);
} else if (oldProfileType == QLatin1String("Banking")) {
lastUsedDirectory = oldBankProfile.readEntry("CsvDirectory");
newProfile = KConfigGroup(config, m_profileConfPrefix.value(Profile::Banking) + QLatin1Char('-') + *profileName);
++profileName;
} else {
if (invalidProfileResponse != QDialogButtonBox::YesToAll && invalidProfileResponse != QDialogButtonBox::NoToAll) {
if (!firstTry &&
!invalidBankProfiles.contains(*profileName)) { // if it isn't first update run and profile isn't on the list of invalid ones then don't bother
++profileName;
continue;
}
invalidProfileResponse = KMessageBox::createKMessageBox(nullptr,
new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::YesToAll |
QDialogButtonBox::No | QDialogButtonBox::NoToAll),
QMessageBox::Warning,
i18n("<center>During update of <b>%1</b><br>"
"the profile type for <b>%2</b> could not be recognized.<br>"
"The profile cannot be used because of that.<br>"
"Do you want to delete it?</center>",
configFilePath, *profileName),
QStringList(), QString(), nullptr, KMessageBox::Dangerous);
}
switch (invalidProfileResponse) {
case QDialogButtonBox::YesToAll:
case QDialogButtonBox::Yes:
oldBankProfile.deleteGroup();
invalidBankProfiles.removeOne(*profileName);
profileName = bankProfiles.erase(profileName);
break;
case QDialogButtonBox::NoToAll:
case QDialogButtonBox::No:
if (!invalidBankProfiles.contains(*profileName)) // on user request: don't delete profile but keep eye on it
invalidBankProfiles.append(*profileName);
ret = false;
++profileName;
break;
}
continue;
}
oldBankProfile.deleteEntry("FileType");
oldBankProfile.deleteEntry("ProfileName");
oldBankProfile.deleteEntry("DebitFlag");
oldBankProfile.deleteEntry("InvDirectory");
oldBankProfile.deleteEntry("CsvDirectory");
oldBankProfile.sync();
oldBankProfile.copyTo(&newProfile);
oldBankProfile.deleteGroup();
newProfile.writeEntry(m_miscSettingsConfName.value(ConfDirectory), lastUsedDirectory);
newProfile.writeEntry(m_miscSettingsConfName.value(ConfEncoding), "106" /*UTF-8*/ ); // in 4.8 encoding wasn't supported well so set it to utf8 by default
newProfile.sync();
}
}
for (auto profileName = investProfiles.begin(); profileName != investProfiles.end();) {
KConfigGroup investProfile(config, investPrefix + *profileName);
if (!investProfile.exists() && !invalidInvestProfiles.contains(*profileName)) { // if there is reference to profile but no profile then remove this reference
profileName = investProfiles.erase(profileName);
continue;
}
++profileName;
}
profileNamesGroup.writeEntry(m_profileConfPrefix.value(Profile::Banking), bankProfiles); // update profile names as some of them might have been changed
profileNamesGroup.writeEntry(m_profileConfPrefix.value(Profile::Investment), investProfiles);
if (invalidBankProfiles.isEmpty()) // if no invalid profiles then we don't need this variable anymore
profileNamesGroup.deleteEntry("InvalidBank");
else
profileNamesGroup.writeEntry("InvalidBank", invalidBankProfiles);
if (invalidInvestProfiles.isEmpty())
profileNamesGroup.deleteEntry("InvalidInvest");
else
profileNamesGroup.writeEntry("InvalidInvest", invalidInvestProfiles);
if (ret)
QFile::remove(configFilePath + ".bak"); // remove backup if all is ok
return ret;
}
bool CSVImporter::profilesAction(const Profile type, const ProfileAction action, const QString &name, const QString &newname)
{
bool ret = false;
const KSharedConfigPtr config = configFile();
KConfigGroup profileNamesGroup(config, m_confProfileNames);
QString profileTypeStr = m_profileConfPrefix.value(type);
QStringList profiles = profileNamesGroup.readEntry(profileTypeStr, QStringList());
KConfigGroup profileName(config, profileTypeStr + QLatin1Char('-') + name);
switch (action) {
case ProfileAction::UpdateLastUsed:
profileNamesGroup.writeEntry(m_confPriorName + profileTypeStr, profiles.indexOf(name));
break;
case ProfileAction::Add:
if (!profiles.contains(newname)) {
profiles.append(newname);
ret = true;
}
break;
case ProfileAction::Remove:
{
profiles.removeOne(name);
profileName.deleteGroup();
profileName.sync();
ret = true;
break;
}
case ProfileAction::Rename:
{
if (!newname.isEmpty() && name != newname) {
int idx = profiles.indexOf(name);
if (idx != -1) {
profiles[idx] = newname;
KConfigGroup newProfileName(config, profileTypeStr + QLatin1Char('-') + newname);
if (profileName.exists() && !newProfileName.exists()) {
profileName.copyTo(&newProfileName);
profileName.deleteGroup();
profileName.sync();
newProfileName.sync();
ret = true;
}
}
}
break;
}
}
profileNamesGroup.writeEntry(profileTypeStr, profiles);
profileNamesGroup.sync();
return ret;
}
bool CSVImporter::validateDateFormat(const int col)
{
bool isOK = true;
for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) {
QStandardItem* item = m_file->m_model->item(row, col);
QDate dat = m_convertDate->convertDate(item->text());
if (dat == QDate()) {
isOK = false;
break;
}
}
return isOK;
}
bool CSVImporter::validateDecimalSymbols(const QList<int> &columns)
{
bool isOK = true;
foreach (const auto column, columns) {
m_file->m_parse->setDecimalSymbol(m_decimalSymbolIndexMap.value(column));
for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) {
QStandardItem *item = m_file->m_model->item(row, column);
QString rawNumber = item->text();
m_file->m_parse->possiblyReplaceSymbol(rawNumber);
if (m_file->m_parse->invalidConversion() &&
!rawNumber.isEmpty()) { // empty strings are welcome
isOK = false;
break;
}
}
}
return isOK;
}
bool CSVImporter::validateCurrencies(const PricesProfile *profile)
{
if (profile->m_securitySymbol.isEmpty() ||
profile->m_currencySymbol.isEmpty())
return false;
return true;
}
bool CSVImporter::validateSecurity(const PricesProfile *profile)
{
if (profile->m_securitySymbol.isEmpty() ||
profile->m_securityName.isEmpty())
return false;
return true;
}
bool CSVImporter::validateSecurity(const InvestmentProfile *profile)
{
if (profile->m_securitySymbol.isEmpty() ||
profile->m_securityName.isEmpty())
return false;
return true;
}
bool CSVImporter::validateSecurities()
{
QSet<QString> onlySymbols;
QSet<QString> onlyNames;
sortSecurities(onlySymbols, onlyNames, m_mapSymbolName);
if (!onlySymbols.isEmpty() || !onlyNames.isEmpty())
return false;
return true;
}
MyMoneyStatement::Transaction::EAction CSVImporter::processActionTypeField(const InvestmentProfile *profile, const int row, const int col)
{
if (col == -1)
return MyMoneyStatement::Transaction::eaNone;
QString type = m_file->m_model->item(row, col)->text();
QList<MyMoneyStatement::Transaction::EAction> actions;
actions << MyMoneyStatement::Transaction::eaBuy << MyMoneyStatement::Transaction::eaSell << // first and second most frequent action
MyMoneyStatement::Transaction::eaReinvestDividend << MyMoneyStatement::Transaction::eaCashDividend << // we don't want "reinv-dividend" to be accidentaly caught by "dividend"
MyMoneyStatement::Transaction::eaInterest <<
MyMoneyStatement::Transaction::eaShrsin << MyMoneyStatement::Transaction::eaShrsout;
foreach (const auto action, actions) {
if (profile->m_transactionNames.value(action).contains(type, Qt::CaseInsensitive))
return action;
}
return MyMoneyStatement::Transaction::eaNone;
}
validationResultE CSVImporter::validateActionType(MyMoneyStatement::Transaction &tr)
{
validationResultE ret = ValidActionType;
QList<MyMoneyStatement::Transaction::EAction> validActionTypes = createValidActionTypes(tr);
if (validActionTypes.isEmpty())
ret = InvalidActionValues;
else if (!validActionTypes.contains(tr.m_eAction))
ret = NoActionType;
return ret;
}
bool CSVImporter::calculateFee()
{
InvestmentProfile *profile = dynamic_cast<InvestmentProfile *>(m_profile);
if (!profile)
return false;
if ((profile->m_feeRate.isEmpty() || // check whether feeRate...
profile->m_colTypeNum.value(Column::Amount) == -1)) // ...and amount is in place
return false;
QString decimalSymbol;
if (profile->m_decimalSymbol == DecimalSymbol::Auto) {
DecimalSymbol detectedSymbol = detectDecimalSymbol(profile->m_colTypeNum.value(Column::Amount), QString());
if (detectedSymbol == DecimalSymbol::Auto)
return false;
m_file->m_parse->setDecimalSymbol(detectedSymbol);
decimalSymbol = m_file->m_parse->decimalSymbol(detectedSymbol);
} else
decimalSymbol = m_file->m_parse->decimalSymbol(profile->m_decimalSymbol);
MyMoneyMoney feePercent(m_file->m_parse->possiblyReplaceSymbol(profile->m_feeRate)); // convert 0.67% ...
feePercent /= MyMoneyMoney(100); // ... to 0.0067
if (profile->m_minFee.isEmpty())
profile->m_minFee = QString::number(0.00, 'f', 2);
MyMoneyMoney minFee(m_file->m_parse->possiblyReplaceSymbol(profile->m_minFee));
QList<QStandardItem *> items;
for (int row = 0; row < profile->m_startLine; ++row) // fill rows above with whitespace for nice effect with markUnwantedRows
items.append(new QStandardItem(QString()));
for (int row = profile->m_startLine; row <= profile->m_endLine; ++row) {
QString txt, numbers;
bool ok = false;
numbers = txt = m_file->m_model->item(row, profile->m_colTypeNum.value(Column::Amount))->text();
numbers.remove(QRegularExpression(QStringLiteral("[,. ]"))).toInt(&ok);
if (!ok) { // check if it's numerical string...
items.append(new QStandardItem(QString()));
continue; // ...and skip if not (TODO: allow currency symbols and IDs)
}
if (txt.startsWith(QLatin1Char('('))) {
txt.remove(QRegularExpression(QStringLiteral("[()]")));
txt.prepend(QLatin1Char('-'));
}
txt = m_file->m_parse->possiblyReplaceSymbol(txt);
MyMoneyMoney fee(txt);
fee *= feePercent;
if (fee < minFee)
fee = minFee;
txt.setNum(fee.toDouble(), 'f', 4);
txt.replace(QLatin1Char('.'), decimalSymbol); //make sure decimal symbol is uniform in whole line
items.append(new QStandardItem(txt));
}
for (int row = profile->m_endLine + 1; row < m_file->m_rowCount; ++row) // fill rows below with whitespace for nice effect with markUnwantedRows
items.append(new QStandardItem(QString()));
int col = profile->m_colTypeNum.value(Column::Fee, -1);
if (col == -1) { // fee column isn't present
m_file->m_model->appendColumn(items);
++m_file->m_columnCount;
} else if (col >= m_file->m_columnCount) { // column number must have been stored in profile
m_file->m_model->appendColumn(items);
++m_file->m_columnCount;
} else { // fee column is present and has been recalculated
m_file->m_model->removeColumn(m_file->m_columnCount - 1);
m_file->m_model->appendColumn(items);
}
profile->m_colTypeNum[Column::Fee] = m_file->m_columnCount - 1;
return true;
}
DecimalSymbol CSVImporter::detectDecimalSymbol(const int col, const QString &exclude)
{
DecimalSymbol detectedSymbol = DecimalSymbol::Auto;
QString pattern;
QRegularExpression re("^[\\(+-]?\\d+[\\)]?$"); // matches '0' ; '+12' ; '-345' ; '(6789)'
bool dotIsDecimalSeparator = false;
bool commaIsDecimalSeparator = false;
for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) {
QString txt = m_file->m_model->item(row, col)->text();
if (txt.isEmpty()) // nothing to process, so go to next row
continue;
int dotPos = txt.lastIndexOf(QLatin1Char('.')); // get last positions of decimal/thousand separator...
int commaPos = txt.lastIndexOf(QLatin1Char(',')); // ...to be able to determine which one is the last
if (dotPos != -1 && commaPos != -1) {
if (dotPos > commaPos && commaIsDecimalSeparator == false) // follwing case 1,234.56
dotIsDecimalSeparator = true;
else if (dotPos < commaPos && dotIsDecimalSeparator == false) // follwing case 1.234,56
commaIsDecimalSeparator = true;
else // follwing case 1.234,56 and somwhere earlier there was 1,234.56 so unresolvable conflict
return detectedSymbol;
} else if (dotPos != -1) { // follwing case 1.23
if (dotIsDecimalSeparator) // it's already know that dotIsDecimalSeparator
continue;
if (!commaIsDecimalSeparator) // if there is no conflict with comma as decimal separator
dotIsDecimalSeparator = true;
else {
if (txt.count(QLatin1Char('.')) > 1) // follwing case 1.234.567 so OK
continue;
else if (txt.length() - 4 == dotPos) // follwing case 1.234 and somwhere earlier there was 1.234,56 so OK
continue;
else // follwing case 1.23 and somwhere earlier there was 1,23 so unresolvable conflict
return detectedSymbol;
}
} else if (commaPos != -1) { // follwing case 1,23
if (commaIsDecimalSeparator) // it's already know that commaIsDecimalSeparator
continue;
else if (!dotIsDecimalSeparator) // if there is no conflict with dot as decimal separator
commaIsDecimalSeparator = true;
else {
if (txt.count(QLatin1Char(',')) > 1) // follwing case 1,234,567 so OK
continue;
else if (txt.length() - 4 == commaPos) // follwing case 1,234 and somwhere earlier there was 1,234.56 so OK
continue;
else // follwing case 1,23 and somwhere earlier there was 1.23 so unresolvable conflict
return detectedSymbol;
}
} else { // follwing case 123
if (pattern.isEmpty()) {
}
txt.remove(QRegularExpression(QLatin1String("[ ") + QRegularExpression::escape(exclude) + QLatin1String("]")));
QRegularExpressionMatch match = re.match(txt);
if (match.hasMatch()) // if string is pure numerical then go forward...
continue;
else // ...if not then it's non-numerical garbage
return detectedSymbol;
}
}
if (dotIsDecimalSeparator)
detectedSymbol = DecimalSymbol::Dot;
else if (commaIsDecimalSeparator)
detectedSymbol = DecimalSymbol::Comma;
else { // whole column was empty, but we don't want to fail so take OS's decimal symbol
if (QLocale().decimalPoint() == QLatin1Char('.'))
detectedSymbol = DecimalSymbol::Dot;
else
detectedSymbol = DecimalSymbol::Comma;
}
return detectedSymbol;
}
int CSVImporter::detectDecimalSymbols(const QList<int> &columns)
{
int ret = -2;
// get list of used currencies to remove them from col
QList<MyMoneyAccount> accounts;
MyMoneyFile *file = MyMoneyFile::instance();
file->accountList(accounts);
- QList<MyMoneyAccount::accountTypeE> accountTypes;
- accountTypes << MyMoneyAccount::Checkings <<
- MyMoneyAccount::Savings <<
- MyMoneyAccount::Liability <<
- MyMoneyAccount::Checkings <<
- MyMoneyAccount::Savings <<
- MyMoneyAccount::Cash <<
- MyMoneyAccount::CreditCard <<
- MyMoneyAccount::Loan <<
- MyMoneyAccount::Asset <<
- MyMoneyAccount::Liability;
+ QList<eMyMoney::Account> accountTypes;
+ accountTypes << eMyMoney::Account::Checkings <<
+ eMyMoney::Account::Savings <<
+ eMyMoney::Account::Liability <<
+ eMyMoney::Account::Checkings <<
+ eMyMoney::Account::Savings <<
+ eMyMoney::Account::Cash <<
+ eMyMoney::Account::CreditCard <<
+ eMyMoney::Account::Loan <<
+ eMyMoney::Account::Asset <<
+ eMyMoney::Account::Liability;
QSet<QString> currencySymbols;
foreach (const auto account, accounts) {
if (accountTypes.contains(account.accountType())) { // account must actually have currency property
currencySymbols.insert(account.currencyId()); // add currency id
currencySymbols.insert(file->currency(account.currencyId()).tradingSymbol()); // add currency symbol
}
}
QString filteredCurrencies = QStringList(currencySymbols.values()).join("");
QString pattern = QString::fromLatin1("%1%2").arg(QLocale().currencySymbol()).arg(filteredCurrencies);
foreach (const auto column, columns) {
DecimalSymbol detectedSymbol = detectDecimalSymbol(column, pattern);
if (detectedSymbol == DecimalSymbol::Auto) {
ret = column;
return ret;
}
m_decimalSymbolIndexMap.insert(column, detectedSymbol);
}
return ret;
}
-QList<MyMoneyAccount> CSVImporter::findAccounts(const QList<MyMoneyAccount::accountTypeE> &accountTypes, const QString &statementHeader)
+QList<MyMoneyAccount> CSVImporter::findAccounts(const QList<eMyMoney::Account> &accountTypes, const QString &statementHeader)
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyAccount> accountList;
file->accountList(accountList);
QList<MyMoneyAccount> filteredTypes;
QList<MyMoneyAccount> filteredAccounts;
QList<MyMoneyAccount>::iterator account;
QRegularExpression filterOutChars(QStringLiteral("[-., ]"));
foreach (const auto account, accountList) {
if (accountTypes.contains(account.accountType()) && !(account).isClosed())
filteredTypes.append(account);
}
// filter out accounts whose names aren't in statements header
foreach (const auto account, filteredTypes) {
QString txt = account.name();
txt.remove(filterOutChars);
if (txt.isEmpty() || txt.length() < 3)
continue;
if (statementHeader.contains(txt, Qt::CaseInsensitive))
filteredAccounts.append(account);
}
// if filtering returned more results, filter out accounts whose numbers aren't in statements header
if (filteredAccounts.count() > 1) {
for (account = filteredAccounts.begin(); account != filteredAccounts.end();) {
QString txt = (*account).number();
txt.remove(filterOutChars);
if (txt.isEmpty() || txt.length() < 3) {
++account;
continue;
}
if (statementHeader.contains(txt, Qt::CaseInsensitive))
++account;
else
account = filteredAccounts.erase(account);
}
}
// if filtering returned more results, filter out accounts whose numbers are the shortest
if (filteredAccounts.count() > 1) {
for (auto i = 1; i < filteredAccounts.count();) {
auto firstAccNumber = filteredAccounts.at(0).number();
auto secondAccNumber = filteredAccounts.at(i).number();
if (firstAccNumber.length() > secondAccNumber.length()) {
filteredAccounts.removeAt(i);
} else if (firstAccNumber.length() < secondAccNumber.length()) {
filteredAccounts.removeAt(0);
--i;
} else {
++i;
}
}
}
// if filtering returned more results, filter out accounts whose names are the shortest
if (filteredAccounts.count() > 1) {
for (auto i = 1; i < filteredAccounts.count();) {
auto firstAccName = filteredAccounts.at(0).name();
auto secondAccName = filteredAccounts.at(i).name();
if (firstAccName.length() > secondAccName.length()) {
filteredAccounts.removeAt(i);
} else if (firstAccName.length() < secondAccName.length()) {
filteredAccounts.removeAt(0);
--i;
} else {
++i;
}
}
}
// if filtering by name and number didn't return nothing, then try filtering by number only
if (filteredAccounts.isEmpty()) {
foreach (const auto account, filteredTypes) {
QString txt = account.number();
txt.remove(filterOutChars);
if (txt.isEmpty() || txt.length() < 3)
continue;
if (statementHeader.contains(txt, Qt::CaseInsensitive))
filteredAccounts.append(account);
}
}
return filteredAccounts;
}
bool CSVImporter::detectAccount(MyMoneyStatement &st)
{
QString statementHeader;
for (int row = 0; row < m_profile->m_startLine; ++row) // concatenate header for better search
for (int col = 0; col < m_file->m_columnCount; ++col)
statementHeader.append(m_file->m_model->item(row, col)->text());
statementHeader.remove(QRegularExpression(QStringLiteral("[-., ]")));
QList<MyMoneyAccount> accounts;
- QList<MyMoneyAccount::accountTypeE> accountTypes;
+ QList<eMyMoney::Account> accountTypes;
switch(m_profile->type()) {
default:
case Profile::Banking:
- accountTypes << MyMoneyAccount::Checkings <<
- MyMoneyAccount::Savings <<
- MyMoneyAccount::Liability <<
- MyMoneyAccount::Checkings <<
- MyMoneyAccount::Savings <<
- MyMoneyAccount::Cash <<
- MyMoneyAccount::CreditCard <<
- MyMoneyAccount::Loan <<
- MyMoneyAccount::Asset <<
- MyMoneyAccount::Liability;
+ accountTypes << eMyMoney::Account::Checkings <<
+ eMyMoney::Account::Savings <<
+ eMyMoney::Account::Liability <<
+ eMyMoney::Account::Checkings <<
+ eMyMoney::Account::Savings <<
+ eMyMoney::Account::Cash <<
+ eMyMoney::Account::CreditCard <<
+ eMyMoney::Account::Loan <<
+ eMyMoney::Account::Asset <<
+ eMyMoney::Account::Liability;
accounts = findAccounts(accountTypes, statementHeader);
break;
case Profile::Investment:
- accountTypes << MyMoneyAccount::Investment; // take investment accounts...
+ accountTypes << eMyMoney::Account::Investment; // take investment accounts...
accounts = findAccounts(accountTypes, statementHeader); //...and search them in statement header
break;
}
if (accounts.count() == 1) { // set account in statement, if it was the only one match
st.m_strAccountName = accounts.first().name();
st.m_strAccountNumber = accounts.first().number();
st.m_accountId = accounts.first().id();
switch (accounts.first().accountType()) {
- case MyMoneyAccount::Checkings:
+ case eMyMoney::Account::Checkings:
st.m_eType = MyMoneyStatement::etCheckings;
break;
- case MyMoneyAccount::Savings:
+ case eMyMoney::Account::Savings:
st.m_eType = MyMoneyStatement::etSavings;
break;
- case MyMoneyAccount::Investment:
+ case eMyMoney::Account::Investment:
st.m_eType = MyMoneyStatement::etInvestment;
break;
- case MyMoneyAccount::CreditCard:
+ case eMyMoney::Account::CreditCard:
st.m_eType = MyMoneyStatement::etCreditCard;
break;
default:
st.m_eType = MyMoneyStatement::etNone;
}
return true;
}
return false;
}
bool CSVImporter::processBankRow(MyMoneyStatement &st, const BankingProfile *profile, const int row)
{
MyMoneyStatement::Transaction tr;
QString memo;
QString txt;
// process number field
if (profile->m_colTypeNum.value(Column::Number, -1) != -1)
tr.m_strNumber = txt;
// process date field
int col = profile->m_colTypeNum.value(Column::Date, -1);
tr.m_datePosted = processDateField(row, col);
if (tr.m_datePosted == QDate())
return false;
// process payee field
col = profile->m_colTypeNum.value(Column::Payee, -1);
if (col != -1)
tr.m_strPayee = m_file->m_model->item(row, col)->text();
// process memo field
col = profile->m_colTypeNum.value(Column::Memo, -1);
if (col != -1)
memo.append(m_file->m_model->item(row, col)->text());
for (int i = 0; i < profile->m_memoColList.count(); ++i) {
if (profile->m_memoColList.at(i) != col) {
if (!memo.isEmpty())
memo.append(QLatin1Char('\n'));
if (profile->m_memoColList.at(i) < m_file->m_columnCount)
memo.append(m_file->m_model->item(row, profile->m_memoColList.at(i))->text());
}
}
tr.m_strMemo = memo;
// process amount field
col = profile->m_colTypeNum.value(Column::Amount, -1);
tr.m_amount = processAmountField(profile, row, col);
if (col != -1 && profile->m_oppositeSigns) // change signs to opposite if requested by user
tr.m_amount *= MyMoneyMoney(-1);
// process credit/debit field
if (profile->m_colTypeNum.value(Column::Credit, -1) != -1 &&
profile->m_colTypeNum.value(Column::Debit, -1) != -1) {
QString credit = m_file->m_model->item(row, profile->m_colTypeNum.value(Column::Credit))->text();
QString debit = m_file->m_model->item(row, profile->m_colTypeNum.value(Column::Debit))->text();
tr.m_amount = processCreditDebit(credit, debit);
if (!credit.isEmpty() && !debit.isEmpty())
return false;
}
MyMoneyStatement::Split s1;
s1.m_amount = tr.m_amount;
s1.m_strMemo = tr.m_strMemo;
MyMoneyStatement::Split s2 = s1;
s2.m_reconcile = tr.m_reconcile;
s2.m_amount = -s1.m_amount;
// process category field
col = profile->m_colTypeNum.value(Column::Category, -1);
if (col != -1) {
txt = m_file->m_model->item(row, col)->text();
QString accountId = MyMoneyFile::instance()->checkCategory(txt, s1.m_amount, s2.m_amount);
if (!accountId.isEmpty()) {
s2.m_accountId = accountId;
s2.m_strCategoryName = txt;
tr.m_listSplits.append(s2);
}
}
// calculate hash
txt.clear();
for (int i = 0; i < m_file->m_columnCount; ++i)
txt.append(m_file->m_model->item(row, i)->text());
QString hashBase = QString::fromLatin1("%1-%2")
.arg(tr.m_datePosted.toString(Qt::ISODate))
.arg(MyMoneyTransaction::hash(txt));
QString hash;
for (uchar idx = 0; idx < 0xFF; ++idx) { // assuming threre will be no more than 256 transactions with the same hashBase
hash = QString::fromLatin1("%1-%2").arg(hashBase).arg(idx);
QSet<QString>::const_iterator it = m_hashSet.constFind(hash);
if (it == m_hashSet.constEnd())
break;
}
m_hashSet.insert(hash);
tr.m_strBankID = hash;
st.m_listTransactions.append(tr); // Add the MyMoneyStatement::Transaction to the statement
return true;
}
bool CSVImporter::processInvestRow(MyMoneyStatement &st, const InvestmentProfile *profile, const int row)
{
MyMoneyStatement::Transaction tr;
QString memo;
QString txt;
// process date field
int col = profile->m_colTypeNum.value(Column::Date, -1);
tr.m_datePosted = processDateField(row, col);
if (tr.m_datePosted == QDate())
return false;
// process quantity field
col = profile->m_colTypeNum.value(Column::Quantity, -1);
tr.m_shares = processQuantityField(profile, row, col);
// process price field
col = profile->m_colTypeNum.value(Column::Price, -1);
tr.m_price = processPriceField(profile, row, col);
// process amount field
col = profile->m_colTypeNum.value(Column::Amount, -1);
tr.m_amount = processAmountField(profile, row, col);
// process type field
col = profile->m_colTypeNum.value(Column::Type, -1);
tr.m_eAction = processActionTypeField(profile, row, col);
if (!m_isActionTypeValidated && col != -1 && // if action type wasn't validated in wizard then...
validateActionType(tr) != ValidActionType) // ...check if price, amount, quantity is appropriate
return false;
// process fee field
col = profile->m_colTypeNum.value(Column::Fee, -1);
if (col != -1) {
if (profile->m_decimalSymbol == DecimalSymbol::Auto) {
DecimalSymbol decimalSymbol = m_decimalSymbolIndexMap.value(col);
m_file->m_parse->setDecimalSymbol(decimalSymbol);
}
txt = m_file->m_model->item(row, col)->text();
if (txt.startsWith(QLatin1Char('('))) // check if brackets notation is used for negative numbers
txt.remove(QRegularExpression(QStringLiteral("[()]")));
if (txt.isEmpty())
tr.m_fees = MyMoneyMoney();
else {
MyMoneyMoney fee(m_file->m_parse->possiblyReplaceSymbol(txt));
if (profile->m_feeIsPercentage && profile->m_feeRate.isEmpty()) // fee is percent
fee *= tr.m_amount / MyMoneyMoney(100); // as percentage
fee.abs();
tr.m_fees = fee;
}
}
// process symbol and name field
col = profile->m_colTypeNum.value(Column::Symbol, -1);
if (col != -1)
tr.m_strSymbol = m_file->m_model->item(row, col)->text();
col = profile->m_colTypeNum.value(Column::Name, -1);
if (col != -1 &&
tr.m_strSymbol.isEmpty()) { // case in which symbol field is empty
txt = m_file->m_model->item(row, col)->text();
tr.m_strSymbol = m_mapSymbolName.key(txt); // it's all about getting the right symbol
} else if (!profile->m_securitySymbol.isEmpty())
tr.m_strSymbol = profile->m_securitySymbol;
else if (tr.m_strSymbol.isEmpty())
return false;
tr.m_strSecurity = m_mapSymbolName.value(tr.m_strSymbol); // take name from prepared names to avoid potential name mismatch
// process memo field
col = profile->m_colTypeNum.value(Column::Memo, -1);
if (col != -1)
memo.append(m_file->m_model->item(row, col)->text());
for (int i = 0; i < profile->m_memoColList.count(); ++i) {
if (profile->m_memoColList.at(i) != col) {
if (!memo.isEmpty())
memo.append(QLatin1Char('\n'));
if (profile->m_memoColList.at(i) < m_file->m_columnCount)
memo.append(m_file->m_model->item(row, profile->m_memoColList.at(i))->text());
}
}
tr.m_strMemo = memo;
tr.m_strInterestCategory.clear(); // no special category
tr.m_strBrokerageAccount.clear(); // no brokerage account auto-detection
MyMoneyStatement::Split s1;
s1.m_amount = tr.m_amount;
s1.m_strMemo = tr.m_strMemo;
MyMoneyStatement::Split s2 = s1;
s2.m_amount = -s1.m_amount;
s2.m_accountId = MyMoneyFile::instance()->checkCategory(tr.m_strInterestCategory, s1.m_amount, s2.m_amount);
// deduct fees from amount
if (tr.m_eAction == MyMoneyStatement::Transaction::eaCashDividend ||
tr.m_eAction == MyMoneyStatement::Transaction::eaSell ||
tr.m_eAction == MyMoneyStatement::Transaction::eaInterest)
tr.m_amount -= tr.m_fees;
else if (tr.m_eAction == MyMoneyStatement::Transaction::eaBuy) {
if (tr.m_amount.isPositive())
tr.m_amount = -tr.m_amount; //if broker doesn't use minus sings for buy transactions, set it manually here
tr.m_amount -= tr.m_fees;
} else if (tr.m_eAction == MyMoneyStatement::Transaction::eaNone)
tr.m_listSplits.append(s2);
st.m_listTransactions.append(tr); // Add the MyMoneyStatement::Transaction to the statement
return true;
}
bool CSVImporter::processPriceRow(MyMoneyStatement &st, const PricesProfile *profile, const int row)
{
MyMoneyStatement::Price pr;
// process date field
int col = profile->m_colTypeNum.value(Column::Date, -1);
pr.m_date = processDateField(row, col);
if (pr.m_date == QDate())
return false;
// process price field
col = profile->m_colTypeNum.value(Column::Price, -1);
pr.m_amount = processPriceField(profile, row, col);
switch (profile->type()) {
case Profile::CurrencyPrices:
if (profile->m_securitySymbol.isEmpty() || profile->m_currencySymbol.isEmpty())
return false;
pr.m_strSecurity = profile->m_securitySymbol;
pr.m_strCurrency = profile->m_currencySymbol;
break;
case Profile::StockPrices:
if (profile->m_securityName.isEmpty())
return false;
pr.m_strSecurity = profile->m_securityName;
break;
default:
return false;
}
pr.m_sourceName = profile->m_profileName;
st.m_listPrices.append(pr); // Add price to the statement
return true;
}
QDate CSVImporter::processDateField(const int row, const int col)
{
QDate date;
if (col != -1) {
QString txt = m_file->m_model->item(row, col)->text();
date = m_convertDate->convertDate(txt); // Date column
}
return date;
}
MyMoneyMoney CSVImporter::processCreditDebit(QString &credit, QString &debit)
{
MyMoneyMoney amount;
if (m_profile->m_decimalSymbol == DecimalSymbol::Auto)
setupFieldDecimalSymbol(m_profile->m_colTypeNum.value(Column::Credit));
if (credit.startsWith(QLatin1Char('('))) { // check if brackets notation is used for negative numbers
credit.remove(QRegularExpression(QStringLiteral("[()]")));
credit.prepend(QLatin1Char('-'));
}
if (debit.startsWith(QLatin1Char('('))) { // check if brackets notation is used for negative numbers
debit.remove(QRegularExpression(QStringLiteral("[()]")));
debit.prepend(QLatin1Char('-'));
}
if (!credit.isEmpty() && !debit.isEmpty()) { // we do not expect both fields to be non-zero
if (MyMoneyMoney(credit).isZero())
credit = QString();
if (MyMoneyMoney(debit).isZero())
debit = QString();
}
if (!debit.startsWith(QLatin1Char('-')) && !debit.isEmpty()) // ensure debit field is negative
debit.prepend(QLatin1Char('-'));
if (!credit.isEmpty() && debit.isEmpty())
amount = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(credit));
else if (credit.isEmpty() && !debit.isEmpty())
amount = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(debit));
else if (!credit.isEmpty() && !debit.isEmpty()) { // both fields are non-empty and non-zero so let user decide
return amount;
} else
amount = MyMoneyMoney(); // both fields are empty and zero so set amount to zero
return amount;
}
MyMoneyMoney CSVImporter::processQuantityField(const CSVProfile *profile, const int row, const int col)
{
MyMoneyMoney shares;
if (col != -1) {
if (profile->m_decimalSymbol == DecimalSymbol::Auto)
setupFieldDecimalSymbol(col);
QString txt = m_file->m_model->item(row, col)->text();
txt.remove(QRegularExpression(QStringLiteral("-+"))); // remove unwanted sings in quantity
if (!txt.isEmpty())
shares = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(txt));
}
return shares;
}
MyMoneyMoney CSVImporter::processAmountField(const CSVProfile *profile, const int row, const int col)
{
MyMoneyMoney amount;
if (col != -1) {
if (profile->m_decimalSymbol == DecimalSymbol::Auto)
setupFieldDecimalSymbol(col);
QString txt = m_file->m_model->item(row, col)->text();
if (txt.startsWith(QLatin1Char('('))) { // check if brackets notation is used for negative numbers
txt.remove(QRegularExpression(QStringLiteral("[()]")));
txt.prepend(QLatin1Char('-'));
}
if (!txt.isEmpty())
amount = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(txt));
}
return amount;
}
MyMoneyMoney CSVImporter::processPriceField(const InvestmentProfile *profile, const int row, const int col)
{
MyMoneyMoney price;
if (col != -1) {
if (profile->m_decimalSymbol == DecimalSymbol::Auto)
setupFieldDecimalSymbol(col);
QString txt = m_file->m_model->item(row, col)->text();
if (!txt.isEmpty()) {
price = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(txt));
price *= m_priceFractions.at(profile->m_priceFraction);
}
}
return price;
}
MyMoneyMoney CSVImporter::processPriceField(const PricesProfile *profile, const int row, const int col)
{
MyMoneyMoney price;
if (col != -1) {
if (profile->m_decimalSymbol == DecimalSymbol::Auto)
setupFieldDecimalSymbol(col);
QString txt = m_file->m_model->item(row, col)->text();
if (!txt.isEmpty()) {
price = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(txt));
price *= m_priceFractions.at(profile->m_priceFraction);
}
}
return price;
}
QList<MyMoneyStatement::Transaction::EAction> CSVImporter::createValidActionTypes(MyMoneyStatement::Transaction &tr)
{
QList<MyMoneyStatement::Transaction::EAction> validActionTypes;
if (tr.m_shares.isPositive() &&
tr.m_price.isPositive() &&
!tr.m_amount.isZero())
validActionTypes << MyMoneyStatement::Transaction::eaReinvestDividend <<
MyMoneyStatement::Transaction::eaBuy <<
MyMoneyStatement::Transaction::eaSell;
else if (tr.m_shares.isZero() &&
tr.m_price.isZero() &&
!tr.m_amount.isZero())
validActionTypes << MyMoneyStatement::Transaction::eaCashDividend <<
MyMoneyStatement::Transaction::eaInterest;
else if (tr.m_shares.isPositive() &&
tr.m_price.isZero() &&
tr.m_amount.isZero())
validActionTypes << MyMoneyStatement::Transaction::eaShrsin <<
MyMoneyStatement::Transaction::eaShrsout;
return validActionTypes;
}
bool CSVImporter::sortSecurities(QSet<QString>& onlySymbols, QSet<QString>& onlyNames, QMap<QString, QString>& mapSymbolName)
{
QList<MyMoneySecurity> securityList = MyMoneyFile::instance()->securityList();
int symbolCol = m_profile->m_colTypeNum.value(Column::Symbol, -1);
int nameCol = m_profile->m_colTypeNum.value(Column::Name, -1);
// sort by availability of symbol and name
for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) {
QString symbol;
QString name;
if (symbolCol != -1)
symbol = m_file->m_model->item(row, symbolCol)->text().trimmed();
if (nameCol != -1)
name = m_file->m_model->item(row, nameCol)->text().trimmed();
if (!symbol.isEmpty() && !name.isEmpty())
mapSymbolName.insert(symbol, name);
else if (!symbol.isEmpty())
onlySymbols.insert(symbol);
else if (!name.isEmpty())
onlyNames.insert(name);
else
return false;
}
// try to find names for symbols
for (QSet<QString>::iterator symbol = onlySymbols.begin(); symbol != onlySymbols.end();) {
QList<MyMoneySecurity> filteredSecurities;
foreach (const auto sec, securityList) {
if ((*symbol).compare(sec.tradingSymbol(), Qt::CaseInsensitive) == 0)
filteredSecurities.append(sec); // gather all securities that by matched by symbol
}
if (filteredSecurities.count() == 1) { // single security matched by the symbol so...
mapSymbolName.insert(*symbol, filteredSecurities.first().name());
symbol = onlySymbols.erase(symbol); // ...it's no longer unknown
} else if (!filteredSecurities.isEmpty()) { // multiple securities matched by the symbol
// TODO: Ask user which security should we match to
mapSymbolName.insert(*symbol, filteredSecurities.first().name());
symbol = onlySymbols.erase(symbol);
} else // no security matched, so leave it as unknown
++symbol;
}
// try to find symbols for names
for (QSet<QString>::iterator name = onlyNames.begin(); name != onlyNames.end();) {
QList<MyMoneySecurity> filteredSecurities;
foreach (const auto sec, securityList) {
if ((*name).compare(sec.name(), Qt::CaseInsensitive) == 0)
filteredSecurities.append(sec); // gather all securities that by matched by name
}
if (filteredSecurities.count() == 1) { // single security matched by the name so...
mapSymbolName.insert(filteredSecurities.first().tradingSymbol(), *name);
name = onlyNames.erase(name); // ...it's no longer unknown
} else if (!filteredSecurities.isEmpty()) { // multiple securities matched by the name
// TODO: Ask user which security should we match to
mapSymbolName.insert(filteredSecurities.first().tradingSymbol(), *name);
name = onlySymbols.erase(name);
} else // no security matched, so leave it as unknown
++name;
}
return true;
}
void CSVImporter::setupFieldDecimalSymbol(int col) {
m_file->m_parse->setDecimalSymbol(m_decimalSymbolIndexMap.value(col));
}
QList<int> CSVImporter::getNumericalColumns()
{
QList<int> columns;
switch(m_profile->type()) {
case Profile::Banking:
if (m_profile->m_colTypeNum.value(Column::Amount, -1) != -1) {
columns << m_profile->m_colTypeNum.value(Column::Amount);
} else {
columns << m_profile->m_colTypeNum.value(Column::Debit);
columns << m_profile->m_colTypeNum.value(Column::Credit);
}
break;
case Profile::Investment:
columns << m_profile->m_colTypeNum.value(Column::Amount);
columns << m_profile->m_colTypeNum.value(Column::Price);
columns << m_profile->m_colTypeNum.value(Column::Quantity);
if (m_profile->m_colTypeNum.value(Column::Fee, -1) != -1)
columns << m_profile->m_colTypeNum.value(Column::Fee);
break;
case Profile::CurrencyPrices:
case Profile::StockPrices:
columns << m_profile->m_colTypeNum.value(Column::Price);
break;
default:
break;
}
return columns;
}
bool CSVImporter::createStatement(MyMoneyStatement &st)
{
switch (m_profile->type()) {
case Profile::Banking:
{
if (!st.m_listTransactions.isEmpty()) // don't create statement if there is one
return true;
st.m_eType = MyMoneyStatement::etNone;
if (m_autodetect.value(AutoAccountBank))
detectAccount(st);
m_hashSet.clear();
BankingProfile *profile = dynamic_cast<BankingProfile *>(m_profile);
for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row)
if (!processBankRow(st, profile, row)) { // parse fields
st = MyMoneyStatement();
return false;
}
return true;
break;
}
case Profile::Investment:
{
if (!st.m_listTransactions.isEmpty()) // don't create statement if there is one
return true;
st.m_eType = MyMoneyStatement::etInvestment;
if (m_autodetect.value(AutoAccountInvest))
detectAccount(st);
InvestmentProfile *profile = dynamic_cast<InvestmentProfile *>(m_profile);
if ((m_profile->m_colTypeNum.value(Column::Fee, -1) == -1 ||
m_profile->m_colTypeNum.value(Column::Fee, -1) >= m_file->m_columnCount) &&
!profile->m_feeRate.isEmpty()) // fee column has not been calculated so do it now
calculateFee();
for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row)
if (!processInvestRow(st, profile, row)) { // parse fields
st = MyMoneyStatement();
return false;
}
for (QMap<QString, QString>::const_iterator it = m_mapSymbolName.cbegin(); it != m_mapSymbolName.cend(); ++it) {
MyMoneyStatement::Security security;
security.m_strSymbol = it.key();
security.m_strName = it.value();
st.m_listSecurities.append(security);
}
return true;
break;
}
default:
case Profile::CurrencyPrices:
case Profile::StockPrices:
{
if (!st.m_listPrices.isEmpty()) // don't create statement if there is one
return true;
st.m_eType = MyMoneyStatement::etNone;
PricesProfile *profile = dynamic_cast<PricesProfile *>(m_profile);
for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row)
if (!processPriceRow(st, profile, row)) { // parse fields
st = MyMoneyStatement();
return false;
}
for (QMap<QString, QString>::const_iterator it = m_mapSymbolName.cbegin(); it != m_mapSymbolName.cend(); ++it) {
MyMoneyStatement::Security security;
security.m_strSymbol = it.key();
security.m_strName = it.value();
st.m_listSecurities.append(security);
}
return true;
}
}
return true;
}
void CSVProfile::readSettings(const KConfigGroup &profilesGroup)
{
m_lastUsedDirectory = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfDirectory), QString());
m_startLine = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfStartLine), 0);
m_trailerLines = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfTrailerLines), 0);
m_encodingMIBEnum = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfEncoding), 106 /* UTF-8 */);
m_dateFormat = static_cast<DateFormat>(profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfDateFormat), (int)DateFormat::YearMonthDay));
m_textDelimiter = static_cast<TextDelimiter>(profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfTextDelimiter), (int)TextDelimiter::DoubleQuote));
m_fieldDelimiter = static_cast<FieldDelimiter>(profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfFieldDelimiter), (int)FieldDelimiter::Auto));
m_decimalSymbol = static_cast<DecimalSymbol>(profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfDecimalSymbol), (int)DecimalSymbol::Auto));
initColNumType();
}
void CSVProfile::writeSettings(KConfigGroup &profilesGroup)
{
QFileInfo fileInfo (m_lastUsedDirectory);
if (fileInfo.isFile())
m_lastUsedDirectory = fileInfo.absolutePath();
if (m_lastUsedDirectory.startsWith(QDir::homePath())) // replace /home/user with ~/ for brevity
m_lastUsedDirectory.replace(0, QDir::homePath().length(), QLatin1Char('~'));
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfDirectory), m_lastUsedDirectory);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfEncoding), m_encodingMIBEnum);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfDateFormat), (int)m_dateFormat);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfFieldDelimiter), (int)m_fieldDelimiter);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfTextDelimiter), (int)m_textDelimiter);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfDecimalSymbol), (int)m_decimalSymbol);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfStartLine), m_startLine);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfTrailerLines), m_trailerLines);
}
bool BankingProfile::readSettings(const KSharedConfigPtr &config)
{
bool exists = true;
KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName);
if (!profilesGroup.exists())
exists = false;
m_colTypeNum[Column::Payee] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Payee), -1);
m_colTypeNum[Column::Number] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Number), -1);
m_colTypeNum[Column::Amount] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Amount), -1);
m_colTypeNum[Column::Debit] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Debit), -1);
m_colTypeNum[Column::Credit] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Credit), -1);
m_colTypeNum[Column::Date] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Date), -1);
m_colTypeNum[Column::Category] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Category), -1);
m_colTypeNum[Column::Memo] = -1; // initialize, otherwise random data may go here
m_oppositeSigns = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfOppositeSigns), 0);
m_memoColList = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Memo), QList<int>());
CSVProfile::readSettings(profilesGroup);
return exists;
}
void BankingProfile::writeSettings(const KSharedConfigPtr &config)
{
KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName);
CSVProfile::writeSettings(profilesGroup);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfOppositeSigns), m_oppositeSigns);
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Payee),
m_colTypeNum.value(Column::Payee));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Number),
m_colTypeNum.value(Column::Number));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Amount),
m_colTypeNum.value(Column::Amount));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Debit),
m_colTypeNum.value(Column::Debit));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Credit),
m_colTypeNum.value(Column::Credit));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Date),
m_colTypeNum.value(Column::Date));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Category),
m_colTypeNum.value(Column::Category));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Memo),
m_memoColList);
profilesGroup.config()->sync();
}
bool InvestmentProfile::readSettings(const KSharedConfigPtr &config)
{
bool exists = true;
KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName);
if (!profilesGroup.exists())
exists = false;
m_transactionNames[MyMoneyStatement::Transaction::eaBuy] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaBuy),
QString(i18nc("Type of operation as in financial statement", "buy")).split(',', QString::SkipEmptyParts));
m_transactionNames[MyMoneyStatement::Transaction::eaSell] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaSell),
QString(i18nc("Type of operation as in financial statement", "sell,repurchase")).split(',', QString::SkipEmptyParts));
m_transactionNames[MyMoneyStatement::Transaction::eaReinvestDividend] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaReinvestDividend),
QString(i18nc("Type of operation as in financial statement", "reinvest,reinv,re-inv")).split(',', QString::SkipEmptyParts));
m_transactionNames[MyMoneyStatement::Transaction::eaCashDividend] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaCashDividend),
QString(i18nc("Type of operation as in financial statement", "dividend")).split(',', QString::SkipEmptyParts));
m_transactionNames[MyMoneyStatement::Transaction::eaInterest] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaInterest),
QString(i18nc("Type of operation as in financial statement", "interest,income")).split(',', QString::SkipEmptyParts));
m_transactionNames[MyMoneyStatement::Transaction::eaShrsin] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaShrsin),
QString(i18nc("Type of operation as in financial statement", "add,stock dividend,divd reinv,transfer in,re-registration in,journal entry")).split(',', QString::SkipEmptyParts));
m_transactionNames[MyMoneyStatement::Transaction::eaShrsout] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaShrsout),
QString(i18nc("Type of operation as in financial statement", "remove")).split(',', QString::SkipEmptyParts));
m_colTypeNum[Column::Date] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Date), -1);
m_colTypeNum[Column::Type] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Type), -1); //use for type col.
m_colTypeNum[Column::Price] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Price), -1);
m_colTypeNum[Column::Quantity] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Quantity), -1);
m_colTypeNum[Column::Amount] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Amount), -1);
m_colTypeNum[Column::Name] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Name), -1);
m_colTypeNum[Column::Fee] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Fee), -1);
m_colTypeNum[Column::Symbol] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Symbol), -1);
m_colTypeNum[Column::Memo] = -1; // initialize, otherwise random data may go here
m_feeIsPercentage = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfFeeIsPercentage), false);
m_feeRate = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfFeeRate), QString());
m_minFee = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfMinFee), QString());
m_memoColList = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Memo), QList<int>());
m_securityName = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecurityName), QString());
m_securitySymbol = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecuritySymbol), QString());
m_dontAsk = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfDontAsk), 0);
m_priceFraction = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfPriceFraction), 2);
CSVProfile::readSettings(profilesGroup);
return exists;
}
void InvestmentProfile::writeSettings(const KSharedConfigPtr &config)
{
KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName);
CSVProfile::writeSettings(profilesGroup);
profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaBuy),
m_transactionNames.value(MyMoneyStatement::Transaction::eaBuy));
profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaSell),
m_transactionNames.value(MyMoneyStatement::Transaction::eaSell));
profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaReinvestDividend),
m_transactionNames.value(MyMoneyStatement::Transaction::eaReinvestDividend));
profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaCashDividend),
m_transactionNames.value(MyMoneyStatement::Transaction::eaCashDividend));
profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaInterest),
m_transactionNames.value(MyMoneyStatement::Transaction::eaInterest));
profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaShrsin),
m_transactionNames.value(MyMoneyStatement::Transaction::eaShrsin));
profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaShrsout),
m_transactionNames.value(MyMoneyStatement::Transaction::eaShrsout));
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfPriceFraction), m_priceFraction);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfFeeIsPercentage), m_feeIsPercentage);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfFeeRate), m_feeRate);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfMinFee), m_minFee);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecurityName), m_securityName);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecuritySymbol), m_securitySymbol);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfDontAsk), m_dontAsk);
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Date),
m_colTypeNum.value(Column::Date));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Type),
m_colTypeNum.value(Column::Type));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Quantity),
m_colTypeNum.value(Column::Quantity));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Amount),
m_colTypeNum.value(Column::Amount));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Price),
m_colTypeNum.value(Column::Price));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Symbol),
m_colTypeNum.value(Column::Symbol));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Name),
m_colTypeNum.value(Column::Name));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Fee),
m_colTypeNum.value(Column::Fee));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Memo),
m_memoColList);
profilesGroup.config()->sync();
}
bool PricesProfile::readSettings(const KSharedConfigPtr &config)
{
bool exists = true;
KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName);
if (!profilesGroup.exists())
exists = false;
m_colTypeNum[Column::Date] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Date), -1);
m_colTypeNum[Column::Price] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(Column::Price), -1);
m_priceFraction = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfPriceFraction), 2);
m_securityName = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecurityName), QString());
m_securitySymbol = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecuritySymbol), QString());
m_currencySymbol = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfCurrencySymbol), QString());
m_dontAsk = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfDontAsk), 0);
CSVProfile::readSettings(profilesGroup);
return exists;
}
void PricesProfile::writeSettings(const KSharedConfigPtr &config)
{
KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName);
CSVProfile::writeSettings(profilesGroup);
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Date),
m_colTypeNum.value(Column::Date));
profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(Column::Price),
m_colTypeNum.value(Column::Price));
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfPriceFraction), m_priceFraction);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecurityName), m_securityName);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecuritySymbol), m_securitySymbol);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfCurrencySymbol), m_currencySymbol);
profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfDontAsk), m_dontAsk);
profilesGroup.config()->sync();
}
CSVFile::CSVFile()
{
m_parse = new Parse;
m_model = new QStandardItemModel;
}
CSVFile::~CSVFile()
{
delete m_parse;
delete m_model;
}
void CSVFile::getStartEndRow(CSVProfile *profile)
{
profile->m_endLine = m_rowCount - 1;
if (profile->m_endLine > profile->m_trailerLines)
profile->m_endLine -= profile->m_trailerLines;
if (profile->m_startLine > profile->m_endLine) // Don't allow m_startLine > m_endLine
profile->m_startLine = profile->m_endLine;
}
void CSVFile::getColumnCount(CSVProfile *profile, const QStringList &rows)
{
if (rows.isEmpty())
return;
QVector<FieldDelimiter> delimiterIndexes;
if (profile->m_fieldDelimiter == FieldDelimiter::Auto)
delimiterIndexes = QVector<FieldDelimiter>{FieldDelimiter::Comma, FieldDelimiter::Semicolon, FieldDelimiter::Colon, FieldDelimiter::Tab}; // include all delimiters to test or ...
else
delimiterIndexes = QVector<FieldDelimiter>{profile->m_fieldDelimiter}; // ... only the one specified
QList<int> totalDelimiterCount({0, 0, 0, 0}); // Total in file for each delimiter
QList<int> thisDelimiterCount({0, 0, 0, 0}); // Total in this line for each delimiter
int colCount = 0; // Total delimiters in this line
FieldDelimiter possibleDelimiter = FieldDelimiter::Comma;
m_columnCount = 0;
foreach (const auto row, rows) {
foreach(const auto delimiterIndex, delimiterIndexes) {
m_parse->setFieldDelimiter(delimiterIndex);
colCount = m_parse->parseLine(row).count(); // parse each line using each delimiter
if (colCount > thisDelimiterCount.at((int)delimiterIndex))
thisDelimiterCount[(int)delimiterIndex] = colCount;
if (thisDelimiterCount[(int)delimiterIndex] > m_columnCount)
m_columnCount = thisDelimiterCount.at((int)delimiterIndex);
totalDelimiterCount[(int)delimiterIndex] += colCount;
if (totalDelimiterCount.at((int)delimiterIndex) > totalDelimiterCount.at((int)possibleDelimiter))
possibleDelimiter = delimiterIndex;
}
}
if (delimiterIndexes.count() != 1) // if purpose was to autodetect...
profile->m_fieldDelimiter = possibleDelimiter; // ... then change field delimiter
m_parse->setFieldDelimiter(profile->m_fieldDelimiter); // restore original field delimiter
}
bool CSVFile::getInFileName(QString inFileName)
{
QFileInfo fileInfo;
if (!inFileName.isEmpty()) {
if (inFileName.startsWith(QLatin1Char('~')))
inFileName.replace(0, 1, QDir::homePath());
fileInfo = QFileInfo(inFileName);
if (fileInfo.isFile()) { // if it is file...
if (fileInfo.exists()) { // ...and exists...
m_inFileName = inFileName; // ...then set as valid filename
return true; // ...and return success...
} else { // ...but if not...
fileInfo.setFile(fileInfo.absolutePath()); //...then set start directory to directory of that file...
if (!fileInfo.exists()) //...and if it doesn't exist too...
fileInfo.setFile(QDir::homePath()); //...then set start directory to home path
}
} else if (fileInfo.isDir()) {
if (fileInfo.exists())
fileInfo = QFileInfo(inFileName);
else
fileInfo.setFile(QDir::homePath());
}
} else
fileInfo = QFileInfo(QDir::homePath());
QPointer<QFileDialog> dialog = new QFileDialog(nullptr, QString(),
fileInfo.absoluteFilePath(),
i18n("CSV Files (*.csv)"));
dialog->setFileMode(QFileDialog::ExistingFile);
QUrl url;
if (dialog->exec() == QDialog::Accepted)
url = dialog->selectedUrls().first();
delete dialog;
if (url.isEmpty()) {
m_inFileName.clear();
return false;
} else
m_inFileName = url.toDisplayString(QUrl::PreferLocalFile);
return true;
}
void CSVFile::setupParser(CSVProfile *profile)
{
if (profile->m_decimalSymbol != DecimalSymbol::Auto)
m_parse->setDecimalSymbol(profile->m_decimalSymbol);
m_parse->setFieldDelimiter(profile->m_fieldDelimiter);
m_parse->setTextDelimiter(profile->m_textDelimiter);
}
void CSVFile::readFile(CSVProfile *profile)
{
QFile inFile(m_inFileName);
if (!inFile.exists())
return;
inFile.open(QIODevice::ReadOnly);
QTextStream inStream(&inFile);
QTextCodec* codec = QTextCodec::codecForMib(profile->m_encodingMIBEnum);
inStream.setCodec(codec);
QString buf = inStream.readAll();
inFile.close();
m_parse->setTextDelimiter(profile->m_textDelimiter);
QStringList rows = m_parse->parseFile(buf); // parse the buffer
m_rowCount = m_parse->lastLine(); // won't work without above line
getColumnCount(profile, rows);
getStartEndRow(profile);
// prepare model from rows having rowCount and columnCount
m_model->clear();
for (int i = 0; i < m_rowCount; ++i) {
QList<QStandardItem*> itemList;
QStringList columns = m_parse->parseLine(rows.takeFirst()); // take instead of read from rows to preserve memory
for (int j = 0; j < m_columnCount; ++j)
itemList.append(new QStandardItem(columns.value(j, QString())));
m_model->appendRow(itemList);
}
}
diff --git a/kmymoney/plugins/csvimport/csvimporter.h b/kmymoney/plugins/csvimport/csvimporter.h
index 1cf7f1348..6d9a9ea32 100644
--- a/kmymoney/plugins/csvimport/csvimporter.h
+++ b/kmymoney/plugins/csvimport/csvimporter.h
@@ -1,360 +1,360 @@
/***************************************************************************
csvimporter.h
-------------------
begin : Sun May 21 2017
copyright : (C) 2015 by Allan Anderson
email : agander93@gmail.com
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 CSVIMPORTER_H
#define CSVIMPORTER_H
// ----------------------------------------------------------------------------
// KDE Includes
#include <KSharedConfig>
// ----------------------------------------------------------------------------
// QT Includes
#include <QSet>
// Project Includes
#include "mymoneyaccount.h"
#include "mymoneystatement.h"
#include "csvenums.h"
#include "csvimport/kmm_csvimport_core_export.h"
class KConfigGroup;
class QStandardItemModel;
class Parse;
class ConvertDate;
enum autodetectTypeE { AutoFieldDelimiter, AutoDecimalSymbol, AutoDateFormat,
AutoAccountInvest, AutoAccountBank
};
enum miscSettingsE { ConfDirectory, ConfEncoding, ConfDateFormat,
ConfFieldDelimiter, ConfTextDelimiter, ConfDecimalSymbol,
ConfStartLine, ConfTrailerLines,
ConfOppositeSigns,
ConfFeeIsPercentage, ConfFeeRate, ConfMinFee,
ConfSecurityName, ConfSecuritySymbol, ConfCurrencySymbol,
ConfPriceFraction, ConfDontAsk,
ConfHeight, ConfWidth
};
enum validationResultE { ValidActionType, InvalidActionValues, NoActionType };
class KMM_CSVIMPORT_CORE_NO_EXPORT CSVProfile
{
protected:
CSVProfile() {}
CSVProfile(const QString &profileName, int encodingMIBEnum,
int startLine, int trailerLines,
DateFormat dateFormat, FieldDelimiter fieldDelimiter, TextDelimiter textDelimiter, DecimalSymbol decimalSymbol,
QMap<Column, int> &colTypeNum) :
m_profileName(profileName), m_encodingMIBEnum(encodingMIBEnum),
m_startLine(startLine), m_trailerLines(trailerLines),
m_dateFormat(dateFormat), m_fieldDelimiter(fieldDelimiter),
m_textDelimiter(textDelimiter), m_decimalSymbol(decimalSymbol),
m_colTypeNum(colTypeNum)
{
initColNumType();
}
void readSettings(const KConfigGroup &profilesGroup);
void writeSettings(KConfigGroup &profilesGroup);
void initColNumType() {
for (auto it = m_colTypeNum.constBegin(); it != m_colTypeNum.constEnd(); ++it)
m_colNumType.insert(it.value(), it.key());
}
public:
virtual ~CSVProfile() {}
virtual Profile type() const = 0;
virtual bool readSettings(const KSharedConfigPtr &config) = 0;
virtual void writeSettings(const KSharedConfigPtr &config) = 0;
QString m_profileName;
QString m_lastUsedDirectory;
int m_encodingMIBEnum;
int m_startLine;
int m_endLine;
int m_trailerLines;
DateFormat m_dateFormat;
FieldDelimiter m_fieldDelimiter;
TextDelimiter m_textDelimiter;
DecimalSymbol m_decimalSymbol;
QMap<Column, int> m_colTypeNum;
QMap<int, Column> m_colNumType;
};
class KMM_CSVIMPORT_CORE_EXPORT BankingProfile : public CSVProfile
{
public:
explicit BankingProfile() : CSVProfile() {}
BankingProfile(QString profileName, int encodingMIBEnum,
int startLine, int trailerLines,
DateFormat dateFormat, FieldDelimiter fieldDelimiter, TextDelimiter textDelimiter, DecimalSymbol decimalSymbol,
QMap<Column, int> colTypeNum,
bool oppositeSigns) :
CSVProfile(profileName, encodingMIBEnum,
startLine, trailerLines,
dateFormat, fieldDelimiter, textDelimiter, decimalSymbol,
colTypeNum),
m_oppositeSigns(oppositeSigns) {}
Profile type() const { return Profile::Banking; }
bool readSettings(const KSharedConfigPtr &config);
void writeSettings(const KSharedConfigPtr &config);
QList<int> m_memoColList;
bool m_oppositeSigns;
};
class KMM_CSVIMPORT_CORE_EXPORT InvestmentProfile : public CSVProfile
{
public:
explicit InvestmentProfile() : CSVProfile() {}
InvestmentProfile(QString profileName, int encodingMIBEnum,
int startLine, int trailerLines,
DateFormat dateFormat, FieldDelimiter fieldDelimiter, TextDelimiter textDelimiter, DecimalSymbol decimalSymbol,
QMap<Column, int> colTypeNum,
int priceFraction, QMap <MyMoneyStatement::Transaction::EAction, QStringList> transactionNames) :
CSVProfile(profileName, encodingMIBEnum,
startLine, trailerLines,
dateFormat, fieldDelimiter, textDelimiter, decimalSymbol,
colTypeNum),
m_transactionNames(transactionNames), m_priceFraction(priceFraction), m_feeIsPercentage(false) {}
Profile type() const { return Profile::Investment; }
bool readSettings(const KSharedConfigPtr &config);
void writeSettings(const KSharedConfigPtr &config);
QMap <MyMoneyStatement::Transaction::EAction, QStringList> m_transactionNames;
QString m_feeRate;
QString m_minFee;
QString m_securityName;
QString m_securitySymbol;
QList<int> m_memoColList;
int m_priceFraction;
int m_dontAsk;
bool m_feeIsPercentage;
};
class KMM_CSVIMPORT_CORE_EXPORT PricesProfile : public CSVProfile
{
public:
explicit PricesProfile() : CSVProfile() {}
explicit PricesProfile(const Profile profileType) : CSVProfile(), m_profileType(profileType) {}
PricesProfile(QString profileName, int encodingMIBEnum,
int startLine, int trailerLines,
DateFormat dateFormat, FieldDelimiter fieldDelimiter, TextDelimiter textDelimiter, DecimalSymbol decimalSymbol,
QMap<Column, int> colTypeNum,
int priceFraction, Profile profileType) :
CSVProfile(profileName, encodingMIBEnum,
startLine, trailerLines,
dateFormat, fieldDelimiter, textDelimiter, decimalSymbol,
colTypeNum),
m_priceFraction(priceFraction), m_profileType(profileType) {}
Profile type() const { return m_profileType; }
bool readSettings(const KSharedConfigPtr &config);
void writeSettings(const KSharedConfigPtr &config);
QString m_securityName;
QString m_securitySymbol;
QString m_currencySymbol;
int m_dontAsk;
int m_priceFraction;
Profile m_profileType;
};
class KMM_CSVIMPORT_CORE_EXPORT CSVFile
{
public:
explicit CSVFile();
~CSVFile();
void getStartEndRow(CSVProfile *profile);
/**
* If delimiter = -1 this method tries different field
* delimiters to get the one with which file has the most columns.
* Otherwise it gets only column count for specified delimiter.
*/
void getColumnCount(CSVProfile *profile, const QStringList &rows);
/**
* This method gets the filename of
* the financial statement.
*/
bool getInFileName(QString startDir = QString());
void setupParser(CSVProfile *profile);
/**
* This method gets file into buffer
* It will laso store file's end column and row.
*/
void readFile(CSVProfile *profile);
Parse *m_parse;
QStandardItemModel *m_model;
QString m_inFileName;
int m_columnCount;
int m_rowCount;
};
class KMM_CSVIMPORT_CORE_EXPORT CSVImporter
{
public:
explicit CSVImporter();
~CSVImporter();
/**
* This method will silently import csv file. Main purpose of this method are online quotes.
*/
MyMoneyStatement unattendedImport(const QString &filename, CSVProfile *profile);
static KSharedConfigPtr configFile();
void profileFactory(const Profile type, const QString &name);
void readMiscSettings();
/**
* This method ensures that configuration file contains all neccesary fields
* and that it is up to date.
*/
void validateConfigFile();
/**
* This method contains routines to update configuration file
* from kmmVer to latest.
*/
bool updateConfigFile(QList<int> &confVer);
/**
* This method will update [ProfileNames] in csvimporterrrc
*/
static bool profilesAction(const Profile type, const ProfileAction action, const QString &name, const QString &newname);
/**
* This methods will ensure that fields of input rows are correct.
*/
bool validateDateFormat(const int col);
bool validateDecimalSymbols(const QList<int> &columns);
bool validateCurrencies(const PricesProfile *profile);
bool validateSecurity(const PricesProfile *profile);
bool validateSecurity(const InvestmentProfile *profile);
bool validateSecurities();
validationResultE validateActionType(MyMoneyStatement::Transaction &tr);
/**
* This method will try to detect decimal symbol in input column.
*/
int detectDecimalSymbols(const QList<int> &columns);
DecimalSymbol detectDecimalSymbol(const int col, const QString &exclude);
/**
* This method will try to detect account from csv header.
*/
- QList<MyMoneyAccount> findAccounts(const QList<MyMoneyAccount::accountTypeE> &accountTypes, const QString &statementHeader);
+ QList<MyMoneyAccount> findAccounts(const QList<eMyMoney::Account> &accountTypes, const QString &statementHeader);
bool detectAccount(MyMoneyStatement &st);
/**
* This methods will evaluate input row and append it to a statement.
*/
bool processBankRow(MyMoneyStatement &st, const BankingProfile *profile, const int row);
bool processInvestRow(MyMoneyStatement &st, const InvestmentProfile *profile, const int row);
bool processPriceRow(MyMoneyStatement &st, const PricesProfile *profile, const int row);
/**
* This methods will evaluate fields of input row and return statement's useful value.
*/
QDate processDateField(const int row, const int col);
MyMoneyMoney processCreditDebit(QString &credit, QString &debit );
MyMoneyMoney processPriceField(const InvestmentProfile *profile, const int row, const int col);
MyMoneyMoney processPriceField(const PricesProfile *profile, const int row, const int col);
MyMoneyMoney processAmountField(const CSVProfile *profile, const int row, const int col);
MyMoneyMoney processQuantityField(const CSVProfile *profile, const int row, const int col);
MyMoneyStatement::Transaction::EAction processActionTypeField(const InvestmentProfile *profile, const int row, const int col);
/**
* This method creates valid set of possible transactions
* according to quantity, amount and price
*/
QList<MyMoneyStatement::Transaction::EAction> createValidActionTypes(MyMoneyStatement::Transaction &tr);
/**
* This method will add fee column to model based on amount and fee rate.
*/
bool calculateFee();
/**
* This method gets securities from investment statement and
* tries to get pairs of symbol and name either
* from KMM or from statement data.
* In case it's not successfull onlySymbols and onlyNames won't be empty.
*/
bool sortSecurities(QSet<QString>& onlySymbols, QSet<QString>& onlyNames, QMap<QString, QString>& mapSymbolName);
/**
* Helper method to set decimal symbol in case it was set to autodetect.
*/
void setupFieldDecimalSymbol(int col);
/**
* Helper method to get all column numbers that were pointed as nummeric
*/
QList<int> getNumericalColumns();
bool createStatement(MyMoneyStatement &st);
ConvertDate *m_convertDate;
CSVFile *m_file;
CSVProfile *m_profile;
KSharedConfigPtr m_config;
bool m_isActionTypeValidated;
QList<MyMoneyMoney> m_priceFractions;
QSet<QString> m_hashSet;
QMap<int, DecimalSymbol> m_decimalSymbolIndexMap;
QMap<QString, QString> m_mapSymbolName;
QMap<autodetectTypeE, bool> m_autodetect;
static const QHash<Column, QString> m_colTypeConfName;
static const QHash<Profile, QString> m_profileConfPrefix;
static const QHash<MyMoneyStatement::Transaction::EAction, QString> m_transactionConfName;
static const QHash<miscSettingsE, QString> m_miscSettingsConfName;
static const QString m_confProfileNames;
static const QString m_confPriorName;
static const QString m_confMiscName;
};
#endif
diff --git a/kmymoney/plugins/csvimport/currenciesdlg.cpp b/kmymoney/plugins/csvimport/currenciesdlg.cpp
index 865402606..14d6447bc 100644
--- a/kmymoney/plugins/csvimport/currenciesdlg.cpp
+++ b/kmymoney/plugins/csvimport/currenciesdlg.cpp
@@ -1,88 +1,89 @@
/*******************************************************************************
* currenciesdlg.cpp
* ------------------
* begin : Sat Jan 21 2017
* copyright : (C) 2017 by Łukasz Wojnilowicz
* 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 "currenciesdlg.h"
#include <QPushButton>
#include "ui_currenciesdlg.h"
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
CurrenciesDlg::CurrenciesDlg() : ui(new Ui::CurrenciesDlg)
{
ui->setupUi(this);
m_buttonOK = ui->buttonBox->button(QDialogButtonBox::Ok);
m_buttonOK->setDefault(true);
m_buttonOK->setShortcut(Qt::CTRL | Qt::Key_Return);
m_buttonOK->setEnabled(false);
connect(ui->cbFrom, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIndexChanged(int)));
connect(ui->cbTo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIndexChanged(int)));
}
CurrenciesDlg::~CurrenciesDlg()
{
delete ui;
}
void CurrenciesDlg::initializeCurrencies(const QString &presetFromCurrency, const QString &presetToCurrency)
{
QList<MyMoneySecurity> currencies = MyMoneyFile::instance()->currencyList();
ui->cbFrom->blockSignals(true);
ui->cbTo->blockSignals(true);
int presetFromIndex = -1;
int presetToIndex = -1;
for (QList<MyMoneySecurity>::const_iterator currency = currencies.cbegin(); currency != currencies.cend(); ++currency) {
QString name = (*currency).name();
QString id = (*currency).id();
QString symbol = (*currency).tradingSymbol();
if (id == presetFromCurrency)
presetFromIndex = ui->cbFrom->count();
if (id == presetToCurrency)
presetToIndex = ui->cbTo->count();
ui->cbFrom->addItem(name + QChar(' ') + QChar('(') + symbol + QChar(')'), QVariant(id));
ui->cbTo->addItem(name + QChar(' ') + QChar('(') + symbol + QChar(')'), QVariant(id));
}
ui->cbFrom->blockSignals(false);
ui->cbTo->blockSignals(false);
ui->cbFrom->setCurrentIndex(presetFromIndex);
ui->cbTo->setCurrentIndex(presetToIndex);
emit ui->cbFrom->currentIndexChanged(presetFromIndex); // in case currentIndex == presetIndex and no signal would be emitted
}
QString CurrenciesDlg::fromCurrency() {
return ui->cbFrom->currentData().toString();
}
QString CurrenciesDlg::toCurrency() {
return ui->cbTo->currentData().toString();
}
int CurrenciesDlg::dontAsk() {
return int(ui->cbDontAsk->isChecked());
}
void CurrenciesDlg::slotIndexChanged(int index)
{
Q_UNUSED(index);
if (ui->cbFrom->currentIndex() != ui->cbTo->currentIndex() &&
ui->cbFrom->currentIndex() != -1 && ui->cbTo->currentIndex() != -1)
m_buttonOK->setEnabled(true);
else
m_buttonOK->setEnabled(false);
}
diff --git a/kmymoney/plugins/csvimport/investmentwizardpage.cpp b/kmymoney/plugins/csvimport/investmentwizardpage.cpp
index 49184c977..1cacdd4e8 100644
--- a/kmymoney/plugins/csvimport/investmentwizardpage.cpp
+++ b/kmymoney/plugins/csvimport/investmentwizardpage.cpp
@@ -1,665 +1,666 @@
/*******************************************************************************
* investmentwizardpage.cpp
* --------------------
* begin : Thur Jan 01 2015
* copyright : (C) 2015 by Allan Anderson
* email : agander93@gmail.com
* copyright : (C) 2016 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 "investmentwizardpage.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QFile>
#include <QStandardItemModel>
#include <QTextStream>
#include <QTableWidget>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KMessageBox>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
#include "csvwizard.h"
#include "csvimporter.h"
#include "transactiondlg.h"
#include "securitydlg.h"
#include "securitiesdlg.h"
#include "ui_investmentwizardpage.h"
#include "ui_securitiesdlg.h"
#include "ui_securitydlg.h"
// ----------------------------------------------------------------------------
InvestmentPage::InvestmentPage(CSVWizard *dlg, CSVImporter *imp) :
CSVWizardPage(dlg, imp),
ui(new Ui::InvestmentPage)
{
ui->setupUi(this);
connect(ui->m_clear, &QAbstractButton::clicked, this, &InvestmentPage::clearColumns);
connect(ui->m_clearFee, &QAbstractButton::clicked, this, &InvestmentPage::clearFee);
connect(ui->m_calculateFee, &QAbstractButton::clicked, this, &InvestmentPage::calculateFee);
// initialize column names
m_dlg->m_colTypeName.insert(Column::Type, i18n("Type"));
m_dlg->m_colTypeName.insert(Column::Price, i18n("Price"));
m_dlg->m_colTypeName.insert(Column::Quantity, i18n("Quantity"));
m_dlg->m_colTypeName.insert(Column::Fee, i18n("Fee"));
m_dlg->m_colTypeName.insert(Column::Date, i18n("Date"));
m_dlg->m_colTypeName.insert(Column::Amount, i18n("Amount"));
m_dlg->m_colTypeName.insert(Column::Symbol, i18n("Symbol"));
m_dlg->m_colTypeName.insert(Column::Name, i18n("Name"));
m_dlg->m_colTypeName.insert(Column::Memo, i18n("Memo"));
m_profile = dynamic_cast<InvestmentProfile *>(m_imp->m_profile);
connect(ui->m_memoCol, SIGNAL(currentIndexChanged(int)), this, SLOT(memoColSelected(int)));
connect(ui->m_typeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(typeColSelected(int)));
connect(ui->m_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(dateColSelected(int)));
connect(ui->m_quantityCol, SIGNAL(currentIndexChanged(int)), this, SLOT(quantityColSelected(int)));
connect(ui->m_priceCol, SIGNAL(currentIndexChanged(int)), this, SLOT(priceColSelected(int)));
connect(ui->m_priceFraction, SIGNAL(currentIndexChanged(int)), this, SLOT(fractionChanged(int)));
connect(ui->m_amountCol, SIGNAL(currentIndexChanged(int)), this, SLOT(amountColSelected(int)));
connect(ui->m_feeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(feeColSelected(int)));
connect(ui->m_symbolCol, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolColSelected(int)));
connect(ui->m_nameCol, SIGNAL(currentIndexChanged(int)), this, SLOT(nameColSelected(int)));
connect(ui->m_feeIsPercentage, &QAbstractButton::clicked, this, &InvestmentPage::feeIsPercentageClicked);
connect(ui->m_feeRate, &QLineEdit::editingFinished, this, &InvestmentPage::feeInputsChanged);
connect(ui->m_feeRate, &QLineEdit::textChanged, this, &InvestmentPage::feeRateChanged);
connect(ui->m_minFee, &QLineEdit::textChanged, this, &InvestmentPage::minFeeChanged);
}
InvestmentPage::~InvestmentPage()
{
delete m_securitiesDlg;
delete ui;
}
void InvestmentPage::calculateFee()
{
m_imp->calculateFee();
m_dlg->updateWindowSize();
m_dlg->markUnwantedRows();
}
void InvestmentPage::initializePage()
{
QHash<Column, QComboBox *> columns {{Column::Amount, ui->m_amountCol}, {Column::Type, ui->m_typeCol},
{Column::Quantity, ui->m_quantityCol}, {Column::Memo, ui->m_memoCol},
{Column::Price, ui->m_priceCol}, {Column::Date, ui->m_dateCol},
{Column::Fee, ui->m_feeCol}, {Column::Symbol, ui->m_symbolCol},
{Column::Name, ui->m_nameCol}};
if (ui->m_dateCol->count() != m_imp->m_file->m_columnCount)
m_dlg->initializeComboBoxes(columns);
ui->m_feeIsPercentage->setChecked(m_profile->m_feeIsPercentage);
columns.remove(Column::Memo);
for (auto it = columns.cbegin(); it != columns.cend(); ++it)
it.value()->setCurrentIndex(m_profile->m_colTypeNum.value(it.key()));
ui->m_priceFraction->blockSignals(true);
foreach (const auto priceFraction, m_imp->m_priceFractions)
ui->m_priceFraction->addItem(QString::number(priceFraction.toDouble(), 'g', 3));
ui->m_priceFraction->blockSignals(false);
ui->m_priceFraction->setCurrentIndex(m_profile->m_priceFraction);
ui->m_feeRate->setText(m_profile->m_feeRate);
ui->m_minFee->setText(m_profile->m_minFee);
ui->m_feeRate->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9]{1,2}[") + QLocale().decimalPoint() + QStringLiteral("]{1,1}[0-9]{0,2}")), this) );
ui->m_minFee->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9]{1,}[") + QLocale().decimalPoint() + QStringLiteral("]{0,1}[0-9]{0,}")), this) );
if (!m_profile->m_feeRate.isEmpty()) { // fee rate indicates that fee column needs to be calculated
if (m_imp->calculateFee()) {
ui->m_feeCol->blockSignals(true);
int feeCol = ui->m_feeCol->count();
ui->m_feeCol->addItem(QString::number(feeCol + 1));
ui->m_feeCol->setEnabled(false);
ui->m_feeCol->setCurrentIndex(feeCol);
ui->m_feeCol->blockSignals(false);
m_dlg->updateWindowSize();
m_dlg->markUnwantedRows();
}
} else if (m_profile->m_colTypeNum.value(Column::Fee, -1) >= ui->m_feeCol->count()) { // no fee rate, calculated fee column index exist, but the column doesn't exist and that's not ok
m_profile->m_colTypeNum[Column::Fee] = -1;
m_profile->m_colNumType.remove(m_profile->m_colNumType.key(Column::Fee));
}
for (int i = 0; i < m_profile->m_memoColList.count(); ++i) { // Set up all memo fields...
int tmp = m_profile->m_memoColList.at(i);
if (tmp < m_profile->m_colTypeNum.count())
ui->m_memoCol->setCurrentIndex(tmp);
}
feeInputsChanged();
}
bool InvestmentPage::isComplete() const
{
return ui->m_dateCol->currentIndex() > -1 &&
ui->m_typeCol->currentIndex() > -1 &&
ui->m_quantityCol->currentIndex() > -1 &&
ui->m_priceCol->currentIndex() > -1 &&
ui->m_amountCol->currentIndex() > -1 &&
ui->m_priceFraction->currentIndex() > -1;
}
bool InvestmentPage::validatePage()
{
if (ui->m_symbolCol->currentIndex() == -1 &&
ui->m_nameCol->currentIndex() == -1)
return validateSecurity();
else
return validateSecurities();
}
void InvestmentPage::cleanupPage()
{
clearFeeCol();
}
void InvestmentPage::memoColSelected(int col)
{
if (m_profile->m_colNumType.value(col) == Column::Type ||
m_profile->m_colNumType.value(col) == Column::Name) {
int rc = KMessageBox::Yes;
if (isVisible())
rc = KMessageBox::questionYesNo(m_dlg, i18n("<center>The '<b>%1</b>' field already has this column selected.</center>"
"<center>If you wish to copy that data to the memo field, click 'Yes'.</center>",
m_dlg->m_colTypeName.value(m_profile->m_colNumType.value(col))));
if (rc == KMessageBox::Yes) {
ui->m_memoCol->setItemText(col, QString::number(col + 1) + QChar(QLatin1Char('*')));
if (!m_profile->m_memoColList.contains(col))
m_profile->m_memoColList.append(col);
} else {
ui->m_memoCol->setItemText(col, QString::number(col + 1));
m_profile->m_memoColList.removeOne(col);
}
//allow only separate memo field occupy combobox
ui->m_memoCol->blockSignals(true);
if (m_profile->m_colTypeNum.value(Column::Memo) != -1)
ui->m_memoCol->setCurrentIndex(m_profile->m_colTypeNum.value(Column::Memo));
else
ui->m_memoCol->setCurrentIndex(-1);
ui->m_memoCol->blockSignals(false);
return;
}
if (m_profile->m_colTypeNum.value(Column::Memo) != -1) // check if this memo has any column 'number' assigned...
m_profile->m_memoColList.removeOne(col); // ...if true remove it from memo list
if(validateSelectedColumn(col, Column::Memo))
if (col != - 1 && !m_profile->m_memoColList.contains(col))
m_profile->m_memoColList.append(col);
}
void InvestmentPage::dateColSelected(int col)
{
validateSelectedColumn(col, Column::Date);
}
void InvestmentPage::feeColSelected(int col)
{
validateSelectedColumn(col, Column::Fee);
feeInputsChanged();
}
void InvestmentPage::typeColSelected(int col)
{
if (validateSelectedColumn(col, Column::Type))
if (!validateMemoComboBox()) // user could have it already in memo so...
memoColSelected(col); // ...if true set memo field again
}
void InvestmentPage::quantityColSelected(int col)
{
validateSelectedColumn(col, Column::Quantity);
}
void InvestmentPage::priceColSelected(int col)
{
validateSelectedColumn(col, Column::Price);
}
void InvestmentPage::amountColSelected(int col)
{
validateSelectedColumn(col, Column::Amount);
clearFeeCol();
feeInputsChanged();
}
void InvestmentPage::symbolColSelected(int col)
{
validateSelectedColumn(col, Column::Symbol);
m_imp->m_mapSymbolName.clear(); // new symbol column so this map is no longer valid
}
void InvestmentPage::nameColSelected(int col)
{
if (validateSelectedColumn(col, Column::Name))
if (!validateMemoComboBox()) // user could have it already in memo so...
memoColSelected(col); // ...if true set memo field again
m_imp->m_mapSymbolName.clear(); // new name column so this map is no longer valid
}
void InvestmentPage::feeIsPercentageClicked(bool checked)
{
m_profile->m_feeIsPercentage = checked;
}
void InvestmentPage::fractionChanged(int col)
{
m_profile->m_priceFraction = col;
emit completeChanged();
}
void InvestmentPage::clearColumns()
{
ui->m_amountCol->setCurrentIndex(-1);
ui->m_dateCol->setCurrentIndex(-1);
ui->m_priceCol->setCurrentIndex(-1);
ui->m_quantityCol->setCurrentIndex(-1);
ui->m_memoCol->setCurrentIndex(-1);
ui->m_typeCol->setCurrentIndex(-1);
ui->m_nameCol->setCurrentIndex(-1);
ui->m_symbolCol->setCurrentIndex(-1);
ui->m_priceFraction->setCurrentIndex(-1);
clearFee();
}
void InvestmentPage::clearFee()
{
clearFeeCol();
ui->m_feeCol->setCurrentIndex(-1);
ui->m_feeIsPercentage->setChecked(false);
ui->m_calculateFee->setEnabled(false);
ui->m_feeRate->setEnabled(true);
ui->m_minFee->setEnabled(false);
ui->m_feeRate->clear();
ui->m_minFee->clear();
}
void InvestmentPage::feeInputsChanged()
{
// if (ui->comboBoxInv_feeCol->currentIndex() < m_importer->m_file->m_columnCount &&
// ui->comboBoxInv_feeCol->currentIndex() > -1) {
// ui->lineEdit_minFee->setEnabled(false);
// ui->lineEdit_feeRate->setEnabled(false);
// ui->lineEdit_minFee->clear();
// ui->lineEdit_feeRate->clear();
// }
if (m_profile->m_feeRate.isEmpty()) {
ui->m_feeCol->setEnabled(true);
ui->m_feeIsPercentage->setEnabled(true);
ui->m_minFee->setEnabled(false);
ui->m_calculateFee->setEnabled(false);
} else {
ui->m_feeCol->setEnabled(false);
ui->m_feeIsPercentage->setEnabled(false);
ui->m_feeIsPercentage->setChecked(true);
ui->m_minFee->setEnabled(true);
ui->m_feeRate->setEnabled(true);
if (m_profile->m_colTypeNum.value(Column::Amount) != -1)
ui->m_calculateFee->setEnabled(true);
}
}
void InvestmentPage::feeRateChanged(const QString &text)
{
m_profile->m_feeRate = text;
}
void InvestmentPage::minFeeChanged(const QString &text)
{
m_profile->m_minFee = text;
}
void InvestmentPage::clearFeeCol()
{
if (!m_profile->m_feeRate.isEmpty() && // if fee rate isn't empty...
m_profile->m_colTypeNum.value(Column::Fee) >= m_imp->m_file->m_columnCount - 1 &&
!ui->m_feeCol->isEnabled()) { // ...and fee column is last...
--m_imp->m_file->m_columnCount;
m_imp->m_file->m_model->removeColumn(m_imp->m_file->m_columnCount);
int feeCol = ui->m_feeCol->currentIndex();
ui->m_feeCol->setCurrentIndex(-1);
ui->m_feeCol->removeItem(feeCol);
m_dlg->updateWindowSize();
}
ui->m_feeCol->setEnabled(true);
ui->m_feeIsPercentage->setEnabled(true);
ui->m_feeIsPercentage->setChecked(false);
}
void InvestmentPage::resetComboBox(const Column comboBox)
{
switch (comboBox) {
case Column::Amount:
ui->m_amountCol->setCurrentIndex(-1);
break;
case Column::Date:
ui->m_dateCol->setCurrentIndex(-1);
break;
case Column::Fee:
ui->m_feeCol->setCurrentIndex(-1);
break;
case Column::Memo:
ui->m_memoCol->setCurrentIndex(-1);
break;
case Column::Price:
ui->m_priceCol->setCurrentIndex(-1);
break;
case Column::Quantity:
ui->m_quantityCol->setCurrentIndex(-1);
break;
case Column::Type:
ui->m_typeCol->setCurrentIndex(-1);
break;
case Column::Symbol:
ui->m_symbolCol->setCurrentIndex(-1);
break;
case Column::Name:
ui->m_nameCol->setCurrentIndex(-1);
break;
default:
KMessageBox::sorry(m_dlg, i18n("<center>Field name not recognised.</center><center>'<b>%1</b>'</center>Please re-enter your column selections.", (int)comboBox), i18n("CSV import"));
}
}
bool InvestmentPage::validateActionType()
{
for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) {
MyMoneyStatement::Transaction tr;
// process quantity field
int col = m_profile->m_colTypeNum.value(Column::Quantity);
tr.m_shares = m_imp->processQuantityField(m_profile, row, col);
// process price field
col = m_profile->m_colTypeNum.value(Column::Price);
tr.m_price = m_imp->processPriceField(m_profile, row, col);
// process amount field
col = m_profile->m_colTypeNum.value(Column::Amount);
tr.m_amount = m_imp->processAmountField(m_profile, row, col);
// process type field
col = m_profile->m_colTypeNum.value(Column::Type);
tr.m_eAction = m_imp->processActionTypeField(m_profile, row, col);
switch(m_imp->validateActionType(tr)) {
case InvalidActionValues:
KMessageBox::sorry(m_dlg,
i18n("The values in the columns you have selected\ndo not match any expected investment type.\n"
"Please check the fields in the current transaction,\nand also your selections."),
i18n("CSV import"));
return false;
break;
case NoActionType:
{
bool unknownType = false;
if (tr.m_eAction == MyMoneyStatement::Transaction::eaNone)
unknownType = true;
QStringList colList;
QStringList colHeaders;
for (int col = 0; col < m_imp->m_file->m_columnCount; ++col) {
colHeaders.append(m_dlg->m_colTypeName.value(m_profile->m_colNumType.value(col, Column::Invalid), QString(i18nc("Unused column", "Unused"))));
colList.append(m_imp->m_file->m_model->item(row, col)->text());
}
QList<MyMoneyStatement::Transaction::EAction> validActionTypes = m_imp->createValidActionTypes(tr);
TransactionDlg* transactionDlg = new TransactionDlg(colList, colHeaders, m_profile->m_colTypeNum.value(Column::Type), validActionTypes);
if (transactionDlg->exec() == QDialog::Rejected) {
KMessageBox::information(m_dlg,
i18n("<center>No valid action type found for this transaction.</center>"
"<center>Please check the parameters supplied.</center>"),
i18n("CSV import"));
return false;
} else
tr.m_eAction = transactionDlg->getActionType();
delete transactionDlg;
if (unknownType) { // type was unknown so store it
col = m_profile->m_colTypeNum.value(Column::Type);
m_profile->m_transactionNames[tr.m_eAction].append(m_imp->m_file->m_model->item(row, col)->text()); // store action type
}
}
default:
break;
}
}
m_imp->m_isActionTypeValidated = true;
return true;
}
bool InvestmentPage::validateSelectedColumn(const int col, const Column type)
{
if (m_profile->m_colTypeNum.value(type) != -1) // check if this 'type' has any column 'number' assigned...
m_profile->m_colNumType.remove(m_profile->m_colTypeNum.value(type)); // ...if true remove 'type' assigned to this column 'number'
bool ret = true;
if (col == -1) { // user only wanted to reset his column so allow him
m_profile->m_colTypeNum[type] = col; // assign new column 'number' to this 'type'
} else if (m_profile->m_colNumType.contains(col)) { // if this column 'number' has already 'type' assigned
KMessageBox::information(m_dlg, i18n("The '<b>%1</b>' field already has this column selected. <center>Please reselect both entries as necessary.</center>",
m_dlg->m_colTypeName.value(m_profile->m_colNumType.value(col))));
resetComboBox(m_profile->m_colNumType[col]);
resetComboBox(type);
ret = false;
} else {
m_profile->m_colTypeNum[type] = col; // assign new column 'number' to this 'type'
m_profile->m_colNumType[col] = type; // assign new 'type' to this column 'number'
}
emit completeChanged();
return ret;
}
bool InvestmentPage::validateMemoComboBox()
{
if (m_profile->m_memoColList.count() == 0)
return true;
for (int i = 0; i < ui->m_memoCol->count(); ++i)
{
QString txt = ui->m_memoCol->itemText(i);
if (txt.contains(QLatin1Char('*'))) // check if text containing '*' belongs to valid column types
if (m_profile->m_colNumType.value(i) != Column::Name &&
m_profile->m_colNumType.value(i) != Column::Type) {
ui->m_memoCol->setItemText(i, QString::number(i + 1));
m_profile->m_memoColList.removeOne(i);
return false;
}
}
return true;
}
bool InvestmentPage::validateSecurities()
{
if (m_securitiesDlg.isNull() &&
m_imp->m_mapSymbolName.isEmpty()) {
QSet<QString> onlySymbols;
QSet<QString> onlyNames;
m_imp->sortSecurities(onlySymbols, onlyNames, m_imp->m_mapSymbolName);
if (!onlySymbols.isEmpty() || !onlyNames.isEmpty()) {
m_securitiesDlg = new SecuritiesDlg;
for (QSet<QString>::const_iterator symbol = onlySymbols.cbegin(); symbol != onlySymbols.cend(); ++symbol)
m_securitiesDlg->displayLine(*symbol, QString());
for (QSet<QString>::const_iterator name = onlyNames.cbegin(); name != onlyNames.cend(); ++name)
m_securitiesDlg->displayLine(QString(), *name);
}
}
if (!m_securitiesDlg.isNull()) {
QTableWidget* symbolTable = m_securitiesDlg->ui->tableWidget;
if (m_securitiesDlg->exec() == QDialog::Rejected) {
return false;
} else {
for (int row = 0; row < symbolTable->rowCount(); ++row) {
QString symbol = symbolTable->item(row, 1)->text();
QString name = symbolTable->item(row, 2)->text();
m_imp->m_mapSymbolName.insert(symbol, name);
}
delete m_securitiesDlg;
}
}
return true;
}
bool InvestmentPage::validateSecurity()
{
if (!m_profile->m_securitySymbol.isEmpty() &&
!m_profile->m_securityName.isEmpty())
m_imp->m_mapSymbolName.insert(m_profile->m_securitySymbol, m_profile->m_securityName);
MyMoneyFile* file = MyMoneyFile::instance();
if (m_securityDlg.isNull() &&
(m_imp->m_mapSymbolName.isEmpty() ||
!(m_profile->m_dontAsk && m_dlg->m_skipSetup))) {
m_securityDlg = new SecurityDlg;
m_securityDlg->initializeSecurities(m_profile->m_securitySymbol, m_profile->m_securityName);
m_securityDlg->ui->cbDontAsk->setChecked(m_profile->m_dontAsk);
}
if (!m_securityDlg.isNull()) {
if (m_securityDlg->exec() == QDialog::Rejected) {
return false;
} else {
QString securityID = m_securityDlg->security();
if (!securityID.isEmpty()) {
m_profile->m_securityName = file->security(securityID).name();
m_profile->m_securitySymbol = file->security(securityID).tradingSymbol();
} else {
m_profile->m_securityName = m_securityDlg->name();
m_profile->m_securitySymbol = m_securityDlg->symbol();
}
m_profile->m_dontAsk = m_securityDlg->dontAsk();
m_imp->m_mapSymbolName.clear();
m_imp->m_mapSymbolName.insert(m_profile->m_securitySymbol, m_profile->m_securityName); // probably we should check if security with given symbol and name exists...
delete m_securityDlg; // ...but KMM allows creating duplicates, so don't bother
}
}
if (m_imp->m_mapSymbolName.isEmpty())
return false;
return true;
}
void InvestmentPage::makeQIF(const MyMoneyStatement& st, const QString& outFileName)
{
QFile oFile(outFileName);
oFile.open(QIODevice::WriteOnly);
QTextStream out(&oFile);
QString buffer;
QString strEType;
switch (st.m_eType) {
case MyMoneyStatement::etInvestment:
default:
strEType = QStringLiteral("Invst");
}
if (!st.m_strAccountName.isEmpty()) {
buffer.append(QStringLiteral("!Account\n"));
buffer.append(QChar(QLatin1Char('N')) + st.m_strAccountName + QChar(QLatin1Char('\n')));
buffer.append(QChar(QLatin1Char('T')) + strEType + QChar(QLatin1Char('\n')));
buffer.append(QStringLiteral("^\n"));
}
for (QList<MyMoneyStatement::Security>::const_iterator it = st.m_listSecurities.constBegin(); it != st.m_listSecurities.constEnd(); ++it) {
buffer.append(QStringLiteral("!Type:Security\n"));
buffer.append(QChar(QLatin1Char('N')) + (*it).m_strName + QChar(QLatin1Char('\n')));
buffer.append(QChar(QLatin1Char('S')) + (*it).m_strSymbol + QChar(QLatin1Char('\n')));
buffer.append(QStringLiteral("TStock\n^\n"));
}
if (!st.m_strAccountName.isEmpty()) {
buffer.append(QStringLiteral("!Account\n"));
buffer.append(QChar(QLatin1Char('N')) + st.m_strAccountName + QChar(QLatin1Char('\n')));
buffer.append(QChar(QLatin1Char('T')) + strEType + QChar(QLatin1Char('\n')));
buffer.append(QStringLiteral("^\n"));
}
buffer.append(QStringLiteral("!Type:") + strEType + QChar(QLatin1Char('\n')));
for (QList<MyMoneyStatement::Transaction>::const_iterator it = st.m_listTransactions.constBegin(); it != st.m_listTransactions.constEnd(); ++it) {
buffer.append(QChar(QLatin1Char('D')) + it->m_datePosted.toString(QStringLiteral("MM/dd/yyyy")) + QChar(QLatin1Char('\n')));
buffer.append(QChar(QLatin1Char('Y')) + it->m_strSecurity + QChar(QLatin1Char('\n')));
QString txt;
switch (it->m_eAction) {
case MyMoneyStatement::Transaction::eaBuy:
txt = QStringLiteral("Buy");
break;
case MyMoneyStatement::Transaction::eaSell:
txt = QStringLiteral("Sell");
break;
case MyMoneyStatement::Transaction::eaReinvestDividend:
txt = QStringLiteral("ReinvDiv");
break;
case MyMoneyStatement::Transaction::eaCashDividend:
txt = QStringLiteral("Div");
break;
case MyMoneyStatement::Transaction::eaInterest:
txt = QStringLiteral("IntInc");
break;
case MyMoneyStatement::Transaction::eaShrsin:
txt = QStringLiteral("ShrsIn");
break;
case MyMoneyStatement::Transaction::eaShrsout:
txt = QStringLiteral("ShrsOut");
break;
case MyMoneyStatement::Transaction::eaStksplit:
txt = QStringLiteral("stksplit");
break;
default:
txt = QStringLiteral("unknown"); // shouldn't happen
}
buffer.append(QChar(QLatin1Char('N')) + txt + QChar(QLatin1Char('\n')));
if (it->m_eAction == MyMoneyStatement::Transaction::eaBuy) // we added 'N' field so buy transaction should have any sign
txt.setNum(it->m_amount.abs().toDouble(), 'f', 4);
else
txt.setNum(it->m_amount.toDouble(), 'f', 4);
buffer.append(QChar(QLatin1Char('T')) + txt + QChar(QLatin1Char('\n')));
txt.setNum(it->m_shares.toDouble(), 'f', 4);
buffer.append(QChar(QLatin1Char('Q')) + txt + QChar(QLatin1Char('\n')));
txt.setNum(it->m_price.toDouble(), 'f', 4);
buffer.append(QChar(QLatin1Char('I')) + txt + QChar(QLatin1Char('\n')));
if (!it->m_fees.isZero()) {
txt.setNum(it->m_fees.toDouble(), 'f', 4);
buffer.append(QChar(QLatin1Char('O')) + txt + QChar(QLatin1Char('\n')));
}
if (!it->m_strBrokerageAccount.isEmpty())
buffer.append(QChar(QLatin1Char('L')) + it->m_strBrokerageAccount + QChar(QLatin1Char('\n')));
if (!it->m_strMemo.isEmpty())
buffer.append(QChar(QLatin1Char('M')) + it->m_strMemo + QChar(QLatin1Char('\n')));
buffer.append(QStringLiteral("^\n"));
out << buffer;// output qif file
buffer.clear();
}
oFile.close();
}
diff --git a/kmymoney/plugins/csvimport/priceswizardpage.cpp b/kmymoney/plugins/csvimport/priceswizardpage.cpp
index 6954c0ec2..de7389c79 100644
--- a/kmymoney/plugins/csvimport/priceswizardpage.cpp
+++ b/kmymoney/plugins/csvimport/priceswizardpage.cpp
@@ -1,233 +1,234 @@
/*******************************************************************************
* priceswizardpage.cpp
* --------------------
* begin : Sat Jan 21 2017
* copyright : (C) 2016 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 "priceswizardpage.h"
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
#include <KMessageBox>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
#include "csvwizard.h"
#include "csvimporter.h"
#include "securitydlg.h"
#include "currenciesdlg.h"
#include "ui_priceswizardpage.h"
#include "ui_currenciesdlg.h"
#include "ui_securitydlg.h"
// ----------------------------------------------------------------------------
PricesPage::PricesPage(CSVWizard *dlg, CSVImporter *imp) :
CSVWizardPage(dlg, imp),
ui(new Ui::PricesPage)
{
ui->setupUi(this);
connect(ui->button_clear, &QAbstractButton::clicked, this, &PricesPage::clearColumns);
m_profile = dynamic_cast<PricesProfile *>(m_imp->m_profile);
// initialize column names
m_dlg->m_colTypeName.insert(Column::Price, i18n("Price"));
m_dlg->m_colTypeName.insert(Column::Date, i18n("Date"));
connect(ui->m_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(dateColSelected(int)));
connect(ui->m_priceCol, SIGNAL(currentIndexChanged(int)), this, SLOT(priceColSelected(int)));
connect(ui->m_priceFraction, SIGNAL(currentIndexChanged(int)), this, SLOT(fractionChanged(int)));
}
PricesPage::~PricesPage()
{
delete m_securityDlg;
delete m_currenciesDlg;
delete ui;
}
void PricesPage::initializePage()
{
const QHash<Column, QComboBox *> columns {{Column::Price, ui->m_priceCol}, {Column::Date, ui->m_dateCol}};
if (ui->m_dateCol->count() != m_imp->m_file->m_columnCount)
m_dlg->initializeComboBoxes(columns);
for (auto it = columns.cbegin(); it != columns.cend(); ++it)
it.value()->setCurrentIndex(m_profile->m_colTypeNum.value(it.key()));
ui->m_priceFraction->blockSignals(true);
foreach (const auto priceFraction, m_imp->m_priceFractions)
ui->m_priceFraction->addItem(QString::number(priceFraction.toDouble(), 'g', 3));
ui->m_priceFraction->blockSignals(false);
ui->m_priceFraction->setCurrentIndex(m_profile->m_priceFraction);
QList<QWizard::WizardButton> layout;
layout << QWizard::Stretch <<
QWizard::BackButton <<
QWizard::NextButton <<
QWizard::CancelButton;
wizard()->setButtonLayout(layout);
}
bool PricesPage::isComplete() const
{
return ui->m_dateCol->currentIndex() > -1 &&
ui->m_priceCol->currentIndex() > -1 &&
ui->m_priceFraction->currentIndex() > -1;
}
bool PricesPage::validatePage()
{
switch (m_imp->m_profile->type()) {
case Profile::CurrencyPrices:
return validateCurrencies();
case Profile::StockPrices:
return validateSecurity();
default:
return false;
}
}
void PricesPage::dateColSelected(int col)
{
validateSelectedColumn(col, Column::Date);
}
void PricesPage::priceColSelected(int col)
{
validateSelectedColumn(col, Column::Price);
}
void PricesPage::fractionChanged(int col)
{
m_profile->m_priceFraction = col;
emit completeChanged();
}
void PricesPage::clearColumns()
{
ui->m_dateCol->setCurrentIndex(-1);
ui->m_priceCol->setCurrentIndex(-1);
ui->m_priceFraction->setCurrentIndex(-1);
}
void PricesPage::resetComboBox(const Column comboBox)
{
switch (comboBox) {
case Column::Date:
ui->m_dateCol->setCurrentIndex(-1);
break;
case Column::Price:
ui->m_priceCol->setCurrentIndex(-1);
break;
default:
KMessageBox::sorry(m_dlg, i18n("<center>Field name not recognised.</center><center>'<b>%1</b>'</center>Please re-enter your column selections.", (int)comboBox), i18n("CSV import"));
}
}
bool PricesPage::validateSelectedColumn(const int col, const Column type)
{
QMap<Column, int> &colTypeNum = m_imp->m_profile->m_colTypeNum;
QMap<int, Column> &colNumType = m_imp->m_profile->m_colNumType;
if (colTypeNum.value(type) != -1) // check if this 'type' has any column 'number' assigned...
colNumType.remove(colTypeNum.value(type)); // ...if true remove 'type' assigned to this column 'number'
bool ret = true;
if (col == -1) { // user only wanted to reset his column so allow him
colTypeNum[type] = col; // assign new column 'number' to this 'type'
} else if (colNumType.contains(col)) { // if this column 'number' has already 'type' assigned
KMessageBox::information(m_dlg, i18n("The '<b>%1</b>' field already has this column selected. <center>Please reselect both entries as necessary.</center>",
m_dlg->m_colTypeName.value(colNumType.value(col))));
resetComboBox(colNumType.value(col));
resetComboBox(type);
ret = false;
} else {
colTypeNum[type] = col; // assign new column 'number' to this 'type'
colNumType[col] = type; // assign new 'type' to this column 'number'
}
emit completeChanged();
return ret;
}
bool PricesPage::validateCurrencies()
{
if ((m_currenciesDlg.isNull() ||
!m_imp->validateCurrencies(m_profile)) &&
!(m_profile->m_dontAsk && m_dlg->m_skipSetup)) {
m_currenciesDlg = new CurrenciesDlg;
m_currenciesDlg->initializeCurrencies(m_profile->m_currencySymbol, m_profile->m_securitySymbol);
m_currenciesDlg->ui->cbDontAsk->setChecked(m_profile->m_dontAsk);
}
if (!m_currenciesDlg.isNull()) {
if (m_currenciesDlg->exec() == QDialog::Rejected) {
return false;
} else {
m_profile->m_currencySymbol = m_currenciesDlg->fromCurrency();
m_profile->m_securitySymbol = m_currenciesDlg->toCurrency();
m_profile->m_dontAsk = m_currenciesDlg->dontAsk();
delete m_currenciesDlg;
}
}
return true;
}
bool PricesPage::validateSecurity()
{
if (m_imp->validateSecurity(m_profile))
m_imp->m_mapSymbolName.insert(m_profile->m_securitySymbol, m_profile->m_securityName);
MyMoneyFile* file = MyMoneyFile::instance();
if (m_securityDlg.isNull() &&
(m_imp->m_mapSymbolName.isEmpty() ||
!(m_profile->m_dontAsk && m_dlg->m_skipSetup))) {
m_securityDlg = new SecurityDlg;
m_securityDlg->initializeSecurities(m_profile->m_securitySymbol, m_profile->m_securityName);
m_securityDlg->ui->cbDontAsk->setChecked(m_profile->m_dontAsk);
}
if (!m_securityDlg.isNull()) {
if (m_securityDlg->exec() == QDialog::Rejected) {
return false;
} else {
QString securityID = m_securityDlg->security();
if (!securityID.isEmpty()) {
m_profile->m_securityName = file->security(securityID).name();
m_profile->m_securitySymbol = file->security(securityID).tradingSymbol();
} else {
m_profile->m_securityName = m_securityDlg->name();
m_profile->m_securitySymbol = m_securityDlg->symbol();
}
m_profile->m_dontAsk = m_securityDlg->dontAsk();
m_imp->m_mapSymbolName.clear();
m_imp->m_mapSymbolName.insert(m_profile->m_securitySymbol, m_profile->m_securityName); // probably we should check if security with given symbol and name exists...
delete m_securityDlg; // ...but KMM allows creating duplicates, so don't bother
}
}
if (m_imp->m_mapSymbolName.isEmpty())
return false;
return true;
}
diff --git a/kmymoney/plugins/csvimport/securitydlg.cpp b/kmymoney/plugins/csvimport/securitydlg.cpp
index 9c0b47fb7..bcae45803 100644
--- a/kmymoney/plugins/csvimport/securitydlg.cpp
+++ b/kmymoney/plugins/csvimport/securitydlg.cpp
@@ -1,104 +1,105 @@
/*******************************************************************************
* securitydlg.cpp
* ------------------
* begin : Sat Jan 15 2017
* copyright : (C) 2017 by Łukasz Wojnilowicz
* 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 "securitydlg.h"
#include <QPushButton>
#include "ui_securitydlg.h"
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
SecurityDlg::SecurityDlg() : ui(new Ui::SecurityDlg)
{
ui->setupUi(this);
m_buttonOK = ui->buttonBox->button(QDialogButtonBox::Ok);
m_buttonOK->setDefault(true);
m_buttonOK->setShortcut(Qt::CTRL | Qt::Key_Return);
m_buttonOK->setEnabled(false);
connect(ui->cbSecurity, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIndexChanged(int)));
connect(ui->leNewSymbol, SIGNAL(editingFinished()), this, SLOT(slotEditingFinished()));
connect(ui->leNewName, SIGNAL(editingFinished()), this, SLOT(slotEditingFinished()));
}
SecurityDlg::~SecurityDlg()
{
delete ui;
}
void SecurityDlg::initializeSecurities(const QString& presetSymbol, const QString& presetName)
{
QList<MyMoneySecurity> securities = MyMoneyFile::instance()->securityList();
if (!securities.isEmpty())
ui->cbSecurity->setEnabled(true);
else {
ui->cbSecurity->setEnabled(false);
return;
}
ui->cbSecurity->blockSignals(true);
int presetIndex = -1;
for (QList<MyMoneySecurity>::const_iterator security = securities.cbegin(); security != securities.cend(); ++security) {
QString symbol = (*security).tradingSymbol();
QString name = (*security).name();
if(symbol == presetSymbol && name == presetName)
presetIndex = ui->cbSecurity->count();
ui->cbSecurity->addItem((*security).name(), QVariant(((*security).id())));
}
ui->cbSecurity->blockSignals(false);
ui->cbSecurity->setCurrentIndex(presetIndex);
emit ui->cbSecurity->currentIndexChanged(presetIndex); // in case currentIndex == presetIndex and no signal would be emitted
}
QString SecurityDlg::security() {
return ui->cbSecurity->currentData().toString();
}
QString SecurityDlg::name() {
return ui->leNewName->text();
}
QString SecurityDlg::symbol() {
return ui->leNewSymbol->text();
}
int SecurityDlg::dontAsk() {
return int(ui->cbDontAsk->isChecked());
}
void SecurityDlg::slotEditingFinished()
{
if (ui->leNewName->text().isEmpty() && ui->leNewSymbol->text().isEmpty()
&& ui->cbSecurity->currentIndex() == -1) {
if (ui->cbSecurity->count() > 0)
ui->cbSecurity->setEnabled(true);
m_buttonOK->setEnabled(false);
} else if (!ui->leNewName->text().isEmpty() || !ui->leNewSymbol->text().isEmpty()) {
ui->cbSecurity->setEnabled(false);
ui->cbSecurity->setCurrentIndex(-1);
if (!ui->leNewName->text().isEmpty() && !ui->leNewSymbol->text().isEmpty())
m_buttonOK->setEnabled(true);
}
}
void SecurityDlg::slotIndexChanged(int index)
{
if (index != -1)
m_buttonOK->setEnabled(true);
else
m_buttonOK->setEnabled(false);
}
diff --git a/kmymoney/plugins/csvimport/tests/csvimporter-test.cpp b/kmymoney/plugins/csvimport/tests/csvimporter-test.cpp
index 06553e5d1..31949de63 100644
--- a/kmymoney/plugins/csvimport/tests/csvimporter-test.cpp
+++ b/kmymoney/plugins/csvimport/tests/csvimporter-test.cpp
@@ -1,329 +1,329 @@
/***************************************************************************
csvimporter-test.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 "csvimporter-test.h"
#include <QtTest/QtTest>
#include <csvimporter.h>
#include "mymoneyfile.h"
#include <mymoneyseqaccessmgr.h>
#include "csvimporttestcommon.h"
QTEST_GUILESS_MAIN(CsvImporterTest)
void CsvImporterTest::initTestCase()
{
// setup the MyMoneyMoney locale settings according to the KDE settings
MyMoneyMoney::setThousandSeparator(QLocale().groupSeparator());
MyMoneyMoney::setDecimalSeparator(QLocale().decimalPoint());
}
void CsvImporterTest::init()
{
storage = new MyMoneySeqAccessMgr;
file = MyMoneyFile::instance();
file->attachStorage(storage);
csvImporter = new CSVImporter;
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, int> {{Column::Date, 0}, {Column::Name, 1}, {Column::Type, 2}, {Column::Quantity, 3}, {Column::Price, 4}, {Column::Amount, 5}},
2,
QMap <MyMoneyStatement::Transaction::EAction, QStringList>{
{MyMoneyStatement::Transaction::eaBuy, QStringList {"buy"}},
{MyMoneyStatement::Transaction::eaSell, QStringList {"sell"}}}
);
pricesProfile = new PricesProfile ("price source",
106, 1, 0, DateFormat::YearMonthDay, FieldDelimiter::Comma,
TextDelimiter::DoubleQuote, DecimalSymbol::Dot,
QMap<Column, int>{{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, int>{{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, int>{{Column::Date, 1}, {Column::Memo, 2}, {Column::Debit, 3}, {Column::Credit, 4}, {Column::Category, 5}},
false);
}
void CsvImporterTest::cleanup()
{
delete investmentProfile;
delete pricesProfile;
delete amountProfile;
delete csvImporter;
file->detachStorage(storage);
delete storage;
}
void CsvImporterTest::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 == MyMoneyStatement::etNone);
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 CsvImporterTest::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 CsvImporterTest::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 CsvImporterTest::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 CsvImporterTest::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 == MyMoneyStatement::etInvestment);
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 CsvImporterTest::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 CsvImporterTest::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 CsvImporterTest::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 CsvImporterTest::testInvAccountAutodetection()
{
MyMoneyFileTransaction ft;
- makeAccount("Eas", "123", MyMoneyAccount::Investment, QDate(2017, 8, 1), file->asset().id());
- makeAccount("BigInvestments", "", MyMoneyAccount::Investment, QDate(2017, 8, 1), file->asset().id());
- makeAccount("BigInvestments", "1234567890", MyMoneyAccount::Investment, QDate(2017, 8, 1), file->asset().id());
- makeAccount("EasyAccount", "", MyMoneyAccount::Investment, QDate(2017, 8, 1), file->asset().id());
- auto toBeClosedAccID = makeAccount("EasyAccount", "123456789", MyMoneyAccount::Investment, QDate(2017, 8, 1), file->asset().id()); // this account has the most characters matching the statement
- auto accID = makeAccount("Easy", "123456789", MyMoneyAccount::Investment, QDate(2017, 8, 1), file->asset().id());
- makeAccount("EasyAccount", "123456789", MyMoneyAccount::Checkings, QDate(2017, 8, 1), file->asset().id());
+ makeAccount("Eas", "123", eMyMoney::Account::Investment, QDate(2017, 8, 1), file->asset().id());
+ makeAccount("BigInvestments", "", eMyMoney::Account::Investment, QDate(2017, 8, 1), file->asset().id());
+ makeAccount("BigInvestments", "1234567890", eMyMoney::Account::Investment, QDate(2017, 8, 1), file->asset().id());
+ makeAccount("EasyAccount", "", eMyMoney::Account::Investment, QDate(2017, 8, 1), file->asset().id());
+ auto toBeClosedAccID = makeAccount("EasyAccount", "123456789", eMyMoney::Account::Investment, QDate(2017, 8, 1), file->asset().id()); // this account has the most characters matching the statement
+ auto accID = makeAccount("Easy", "123456789", eMyMoney::Account::Investment, QDate(2017, 8, 1), file->asset().id());
+ makeAccount("EasyAccount", "123456789", eMyMoney::Account::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 CsvImporterTest::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/csvimport/tests/csvimporttestcommon.cpp b/kmymoney/plugins/csvimport/tests/csvimporttestcommon.cpp
index 936c2bbd2..6fc88c771 100644
--- a/kmymoney/plugins/csvimport/tests/csvimporttestcommon.cpp
+++ b/kmymoney/plugins/csvimport/tests/csvimporttestcommon.cpp
@@ -1,64 +1,66 @@
/***************************************************************************
csvimporttestcommon.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 "csvimporttestcommon.h"
#include <QFile>
#include <QTextStream>
#include "mymoneyfile.h"
+#include "mymoneyaccount.h"
+#include "mymoneysecurity.h"
void writeStatementToCSV(const QString& content, const QString& filename)
{
QFile g(filename);
g.open(QIODevice::WriteOnly);
QTextStream stream (&g);
stream << content;
g.close();
}
QString csvDataset(const int set) {
QString csvContent;
switch (set) {
case 0:
csvContent += QLatin1String("Date;Name;Type;Quantity;Price;Amount;Fee\n");
csvContent += QLatin1String("2017-08-01-12.02.10;Stock 1;buy;100;1.25;125;4\n"); // positive amount here is not good, but KMM can hadle it
csvContent += QLatin1String("2017-08-02-12.02.10;Stock 2;sell;100;4.56;456;6\n");
csvContent += QLatin1String("2017-08-03-12.02.10;Stock 3;buy;200;5.67;1134;4\n");
break;
default:
break;
}
return csvContent;
}
-QString makeAccount(const QString& name, const QString& number, MyMoneyAccount::accountTypeE type, const QDate& open, const QString& parent)
+QString makeAccount(const QString& name, const QString& number, eMyMoney::Account type, const QDate& open, const QString& parent)
{
MyMoneyAccount acc;
MyMoneyFileTransaction ft;
auto file = MyMoneyFile::instance();
acc.setName(name);
acc.setNumber(number);
acc.setAccountType(type);
acc.setOpeningDate(open);
acc.setCurrencyId(file->baseCurrency().id());
auto parentAcc = file->account(parent);
file->addAccount(acc, parentAcc);
ft.commit();
return acc.id();
}
diff --git a/kmymoney/plugins/csvimport/tests/csvimporttestcommon.h b/kmymoney/plugins/csvimport/tests/csvimporttestcommon.h
index d79c74436..7608fb417 100644
--- a/kmymoney/plugins/csvimport/tests/csvimporttestcommon.h
+++ b/kmymoney/plugins/csvimport/tests/csvimporttestcommon.h
@@ -1,25 +1,26 @@
/***************************************************************************
csvimporttestcommon.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 CSVIMPORTTESTCOMMON_H
#define CSVIMPORTTESTCOMMON_H
-#include "mymoneyaccount.h"
+#include "mymoneyenums.h"
class QString;
+class QDate;
extern void writeStatementToCSV(const QString& content, const QString& filename);
extern QString csvDataset(const int set);
-extern QString makeAccount(const QString& name, const QString& number, MyMoneyAccount::accountTypeE type, const QDate& open, const QString& parent);
+extern QString makeAccount(const QString& name, const QString& number, eMyMoney::Account type, const QDate& open, const QString& parent);
#endif
diff --git a/kmymoney/plugins/ofximport/dialogs/mymoneyofxconnector.cpp b/kmymoney/plugins/ofximport/dialogs/mymoneyofxconnector.cpp
index 0293ead8d..9bddf73d2 100644
--- a/kmymoney/plugins/ofximport/dialogs/mymoneyofxconnector.cpp
+++ b/kmymoney/plugins/ofximport/dialogs/mymoneyofxconnector.cpp
@@ -1,772 +1,772 @@
/***************************************************************************
mymoneyofxconnector.cpp
-------------------
begin : Sat Nov 13 2004
copyright : (C) 2002 by Ace Jones
email : acejones@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 "mymoneyofxconnector.h"
// ----------------------------------------------------------------------------
// System Includes
#include <libofx/libofx.h>
// ----------------------------------------------------------------------------
// QT Includes
#include <QDateTime>
#include <QRegExp>
#include <QByteArray>
#include <QList>
#include <QApplication>
#include <QDebug>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KComboBox>
#include <KPasswordDialog>
#include <KWallet>
#include <KMainWindow>
#include <KLineEdit>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyaccount.h"
#include "mymoneykeyvaluecontainer.h"
using KWallet::Wallet;
OfxHeaderVersion::OfxHeaderVersion(KComboBox* combo, const QString& headerVersion) :
m_combo(combo)
{
combo->clear();
combo->addItem("102");
combo->addItem("103");
if (!headerVersion.isEmpty()) {
combo->setCurrentItem(headerVersion);
} else {
combo->setCurrentItem("102");
}
}
QString OfxHeaderVersion::headerVersion() const
{
return m_combo->currentText();
}
OfxAppVersion::OfxAppVersion(KComboBox* combo, KLineEdit* versionEdit, const QString& appId) :
m_combo(combo),
m_versionEdit(versionEdit)
{
// http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-intuit-products/
// http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-microsoft-money/
// Quicken
m_appMap[i18n("Quicken Windows 2003")] = "QWIN:1200";
m_appMap[i18n("Quicken Windows 2004")] = "QWIN:1300";
m_appMap[i18n("Quicken Windows 2005")] = "QWIN:1400";
m_appMap[i18n("Quicken Windows 2006")] = "QWIN:1500";
m_appMap[i18n("Quicken Windows 2007")] = "QWIN:1600";
m_appMap[i18n("Quicken Windows 2008")] = "QWIN:1700";
// the following three added as found on
// https://microsoftmoneyoffline.wordpress.com/appid-appver/ on 2013-02-28
m_appMap[i18n("Quicken Windows 2010")] = "QWIN:1800";
m_appMap[i18n("Quicken Windows 2011")] = "QWIN:1900";
m_appMap[i18n("Quicken Windows 2012")] = "QWIN:2100";
m_appMap[i18n("Quicken Windows 2013")] = "QWIN:2200";
m_appMap[i18n("Quicken Windows 2014")] = "QWIN:2300";
// following two added as found in previous URL on 2017-10-01
m_appMap[i18n("Quicken Windows 2015")] = "QWIN:2400";
m_appMap[i18n("Quicken Windows 2016")] = "QWIN:2500";
m_appMap[i18n("Quicken Windows (Expert)")] = "QWIN:";
// MS-Money
m_appMap[i18n("MS-Money 2003")] = "Money:1100";
m_appMap[i18n("MS-Money 2004")] = "Money:1200";
m_appMap[i18n("MS-Money 2005")] = "Money:1400";
m_appMap[i18n("MS-Money 2006")] = "Money:1500";
m_appMap[i18n("MS-Money 2007")] = "Money:1600";
m_appMap[i18n("MS-Money Plus")] = "Money:1700";
m_appMap[i18n("MS-Money (Expert)")] = "Money:";
// KMyMoney
m_appMap["KMyMoney"] = "KMyMoney:1000";
combo->clear();
combo->addItems(m_appMap.keys());
if (versionEdit)
versionEdit->hide();
QMap<QString, QString>::const_iterator it_a;
// check for an exact match
for (it_a = m_appMap.constBegin(); it_a != m_appMap.constEnd(); ++it_a) {
if (*it_a == appId)
break;
}
// not found, check if we have a manual version of this product
QRegExp appExp("(\\w+:)(\\d+)");
if (it_a == m_appMap.constEnd()) {
if (appExp.exactMatch(appId)) {
for (it_a = m_appMap.constBegin(); it_a != m_appMap.constEnd(); ++it_a) {
if (*it_a == appExp.cap(1))
break;
}
}
}
// if we still haven't found it, we use a default as last resort
if (it_a != m_appMap.constEnd()) {
combo->setCurrentItem(it_a.key());
if ((*it_a).endsWith(':')) {
if (versionEdit) {
versionEdit->show();
versionEdit->setText(appExp.cap(2));
} else {
combo->setCurrentItem(i18n("Quicken Windows 2008"));
}
}
} else {
combo->setCurrentItem(i18n("Quicken Windows 2008"));
}
}
const QString OfxAppVersion::appId() const
{
static QString defaultAppId("QWIN:1700");
QString app = m_combo->currentText();
if (m_appMap[app] != defaultAppId) {
if (m_appMap[app].endsWith(':')) {
if (m_versionEdit) {
return m_appMap[app] + m_versionEdit->text();
} else {
return QString();
}
}
return m_appMap[app];
}
return QString();
}
bool OfxAppVersion::isValid() const
{
QRegExp exp(".+:\\d+");
QString app = m_combo->currentText();
if (m_appMap[app].endsWith(':')) {
if (m_versionEdit) {
app = m_appMap[app] + m_versionEdit->text();
} else {
app.clear();
}
} else {
app = m_appMap[app];
}
return exp.exactMatch(app);
}
MyMoneyOfxConnector::MyMoneyOfxConnector(const MyMoneyAccount& _account):
m_account(_account)
{
m_fiSettings = m_account.onlineBankingSettings();
}
QString MyMoneyOfxConnector::iban() const
{
return m_fiSettings.value("bankid");
}
QString MyMoneyOfxConnector::fiorg() const
{
return m_fiSettings.value("org");
}
QString MyMoneyOfxConnector::fiid() const
{
return m_fiSettings.value("fid");
}
QString MyMoneyOfxConnector::clientUid() const
{
return m_fiSettings.value("clientUid");
}
QString MyMoneyOfxConnector::username() const
{
return m_fiSettings.value("username");
}
QString MyMoneyOfxConnector::password() const
{
// if we don't find a password in the wallet, we use the old method
// and retrieve it from the settings stored in the KMyMoney data storage.
// in case we don't have a password on file, we ask the user
QString key = OFX_PASSWORD_KEY(m_fiSettings.value("url"), m_fiSettings.value("uniqueId"));
QString pwd = m_fiSettings.value("password");
// now check for the wallet
Wallet *wallet = openSynchronousWallet();
if (wallet
&& !Wallet::keyDoesNotExist(Wallet::NetworkWallet(), Wallet::PasswordFolder(), key)) {
wallet->setFolder(Wallet::PasswordFolder());
wallet->readPassword(key, pwd);
}
if (pwd.isEmpty()) {
QPointer<KPasswordDialog> dlg = new KPasswordDialog(0);
dlg->setPrompt(i18n("Enter your password for account <b>%1</b>").arg(m_account.name()));
if (dlg->exec())
pwd = dlg->password();
delete dlg;
}
return pwd;
}
QString MyMoneyOfxConnector::accountnum() const
{
return m_fiSettings.value("accountid");
}
QString MyMoneyOfxConnector::url() const
{
return m_fiSettings.value("url");
}
QDate MyMoneyOfxConnector::statementStartDate() const
{
if ((m_fiSettings.value("kmmofx-todayMinus").toInt() != 0) && !m_fiSettings.value("kmmofx-numRequestDays").isEmpty()) {
return QDate::currentDate().addDays(-m_fiSettings.value("kmmofx-numRequestDays").toInt());
} else if ((m_fiSettings.value("kmmofx-lastUpdate").toInt() != 0) && !m_account.value("lastImportedTransactionDate").isEmpty()) {
return QDate::fromString(m_account.value("lastImportedTransactionDate"), Qt::ISODate);
} else if ((m_fiSettings.value("kmmofx-pickDate").toInt() != 0) && !m_fiSettings.value("kmmofx-specificDate").isEmpty()) {
return QDate::fromString(m_fiSettings.value("kmmofx-specificDate"));
}
return QDate::currentDate().addMonths(-2);
}
OfxAccountData::AccountType MyMoneyOfxConnector::accounttype() const
{
OfxAccountData::AccountType result = OfxAccountData::OFX_CHECKING;
QString type = m_account.onlineBankingSettings()["type"];
if (type == "CHECKING")
result = OfxAccountData::OFX_CHECKING;
else if (type == "SAVINGS")
result = OfxAccountData::OFX_SAVINGS;
else if (type == "MONEY MARKET")
result = OfxAccountData::OFX_MONEYMRKT;
else if (type == "CREDIT LINE")
result = OfxAccountData::OFX_CREDITLINE;
else if (type == "CMA")
result = OfxAccountData::OFX_CMA;
else if (type == "CREDIT CARD")
result = OfxAccountData::OFX_CREDITCARD;
else if (type == "INVESTMENT")
result = OfxAccountData::OFX_INVESTMENT;
else {
switch (m_account.accountType()) {
- case MyMoneyAccount::Investment:
+ case eMyMoney::Account::Investment:
result = OfxAccountData::OFX_INVESTMENT;
break;
- case MyMoneyAccount::CreditCard:
+ case eMyMoney::Account::CreditCard:
result = OfxAccountData::OFX_CREDITCARD;
break;
- case MyMoneyAccount::Savings:
+ case eMyMoney::Account::Savings:
result = OfxAccountData::OFX_SAVINGS;
break;
default:
break;
}
}
// This is a bit of a personalized hack. Sometimes we may want to override the
// ofx type for an account. For now, I will stash it in the notes!
QRegExp rexp("OFXTYPE:([A-Z]*)");
if (rexp.indexIn(m_account.description()) != -1) {
QString override = rexp.cap(1);
qDebug() << "MyMoneyOfxConnector::accounttype() overriding to " << result;
if (override == "BANK")
result = OfxAccountData::OFX_CHECKING;
else if (override == "CC")
result = OfxAccountData::OFX_CREDITCARD;
else if (override == "INV")
result = OfxAccountData::OFX_INVESTMENT;
else if (override == "MONEYMARKET")
result = OfxAccountData::OFX_MONEYMRKT;
}
return result;
}
void MyMoneyOfxConnector::initRequest(OfxFiLogin* fi) const
{
memset(fi, 0, sizeof(OfxFiLogin));
strncpy(fi->fid, fiid().toLatin1(), OFX_FID_LENGTH - 1);
strncpy(fi->org, fiorg().toLatin1(), OFX_ORG_LENGTH - 1);
strncpy(fi->userid, username().toLatin1(), OFX_USERID_LENGTH - 1);
strncpy(fi->userpass, password().toLatin1(), OFX_USERPASS_LENGTH - 1);
#ifdef LIBOFX_HAVE_CLIENTUID
strncpy(fi->clientuid, clientUid().toLatin1(), OFX_CLIENTUID_LENGTH - 1);
#endif
// If we don't know better, we pretend to be Quicken 2008
// http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-intuit-products/
// http://ofxblog.wordpress.com/2007/06/06/ofx-appid-and-appver-for-microsoft-money/
QString appId = m_account.onlineBankingSettings().value("appId");
QRegExp exp("(.*):(.*)");
if (exp.indexIn(appId) != -1) {
strncpy(fi->appid, exp.cap(1).toLatin1(), OFX_APPID_LENGTH - 1);
strncpy(fi->appver, exp.cap(2).toLatin1(), OFX_APPVER_LENGTH - 1);
} else {
strncpy(fi->appid, "QWIN", OFX_APPID_LENGTH - 1);
strncpy(fi->appver, "1700", OFX_APPVER_LENGTH - 1);
}
QString headerVersion = m_account.onlineBankingSettings().value("kmmofx-headerVersion");
if (!headerVersion.isEmpty()) {
strncpy(fi->header_version, headerVersion.toLatin1(), OFX_HEADERVERSION_LENGTH - 1);
}
}
const QByteArray MyMoneyOfxConnector::statementRequest() const
{
OfxFiLogin fi;
initRequest(&fi);
OfxAccountData account;
memset(&account, 0, sizeof(OfxAccountData));
if (!iban().toLatin1().isEmpty()) {
strncpy(account.bank_id, iban().toLatin1(), OFX_BANKID_LENGTH - 1);
strncpy(account.broker_id, iban().toLatin1(), OFX_BROKERID_LENGTH - 1);
}
strncpy(account.account_number, accountnum().toLatin1(), OFX_ACCTID_LENGTH - 1);
account.account_type = accounttype();
QByteArray result;
if (fi.userpass[0]) {
char *szrequest = libofx_request_statement(&fi, &account, QDateTime(statementStartDate()).toTime_t());
QString request = szrequest;
// remove the trailing zero
result = request.toUtf8();
if(result.at(result.size()-1) == 0)
result.truncate(result.size() - 1);
free(szrequest);
}
return result;
}
#if 0
MyMoneyOfxConnector::Tag MyMoneyOfxConnector::message(const QString& _msgType, const QString& _trnType, const Tag& _request)
{
return Tag(_msgType + "MSGSRQV1")
.subtag(Tag(_trnType + "TRNRQ")
.element("TRNUID", uuid())
.element("CLTCOOKIE", "1")
.subtag(_request));
}
MyMoneyOfxConnector::Tag MyMoneyOfxConnector::investmentRequest() const
{
QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
return message("INVSTMT", "INVSTMT", Tag("INVSTMTRQ")
.subtag(Tag("INVACCTFROM").element("BROKERID", fiorg()).element("ACCTID", accountnum()))
.subtag(Tag("INCTRAN").element("DTSTART", dtstart_string).element("INCLUDE", "Y"))
.element("INCOO", "Y")
.subtag(Tag("INCPOS").element("DTASOF", dtnow_string).element("INCLUDE", "Y"))
.element("INCBAL", "Y"));
}
MyMoneyOfxConnector::Tag MyMoneyOfxConnector::bankStatementRequest(const QDate& _dtstart) const
{
QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
return message("BANK", "STMT", Tag("STMTRQ")
.subtag(Tag("BANKACCTFROM").element("BANKID", iban()).element("ACCTID", accountnum()).element("ACCTTYPE", "CHECKING"))
.subtag(Tag("INCTRAN").element("DTSTART", dtstart_string).element("INCLUDE", "Y")));
}
MyMoneyOfxConnector::Tag MyMoneyOfxConnector::creditCardRequest(const QDate& _dtstart) const
{
QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
return message("CREDITCARD", "CCSTMT", Tag("CCSTMTRQ")
.subtag(Tag("CCACCTFROM").element("ACCTID", accountnum()))
.subtag(Tag("INCTRAN").element("DTSTART", dtstart_string).element("INCLUDE", "Y")));
}
MyMoneyOfxConnector::Tag MyMoneyOfxConnector::signOn() const
{
QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
Tag fi("FI");
fi.element("ORG", fiorg());
if (!fiid().isEmpty())
fi.element("FID", fiid());
return Tag("SIGNONMSGSRQV1")
.subtag(Tag("SONRQ")
.element("DTCLIENT", dtnow_string)
.element("USERID", username())
.element("USERPASS", password())
.element("LANGUAGE", "ENG")
.subtag(fi)
.element("APPID", "QWIN")
.element("APPVER", "1100"));
}
QString MyMoneyOfxConnector::header()
{
return QString("OFXHEADER:100\r\n"
"DATA:OFXSGML\r\n"
"VERSION:102\r\n"
"SECURITY:NONE\r\n"
"ENCODING:USASCII\r\n"
"CHARSET:1252\r\n"
"COMPRESSION:NONE\r\n"
"OLDFILEUID:NONE\r\n"
"NEWFILEUID:%1\r\n"
"\r\n").arg(uuid());
}
QString MyMoneyOfxConnector::uuid()
{
static int id = 1;
return QDateTime::currentDateTime().toString("yyyyMMdd-hhmmsszzz-") + QString::number(id++);
}
//
// Methods to provide RESPONSES to OFX requests. This has no real use in
// KMyMoney, but it's included for the purposes of unit testing. This way, I
// can create a MyMoneyAccount, write it to an OFX file, import that OFX file,
// and check that everything made it through the importer.
//
// It's also a far-off dream to write an OFX server using KMyMoney as a
// backend. It really should not be that hard, and it would fill a void in
// the open source software community.
//
const QByteArray MyMoneyOfxConnector::statementResponse(const QDate& _dtstart) const
{
QString request;
if (accounttype() == "CC")
request = header() + Tag("OFX").subtag(signOnResponse()).subtag(creditCardStatementResponse(_dtstart));
else if (accounttype() == "INV")
request = header() + Tag("OFX").subtag(signOnResponse()).data(investmentStatementResponse(_dtstart));
else
request = header() + Tag("OFX").subtag(signOnResponse()).subtag(bankStatementResponse(_dtstart));
// remove the trailing zero
QByteArray result = request.utf8();
result.truncate(result.size() - 1);
return result;
}
MyMoneyOfxConnector::Tag MyMoneyOfxConnector::signOnResponse() const
{
QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
Tag sonrs("SONRS");
sonrs
.subtag(Tag("STATUS")
.element("CODE", "0")
.element("SEVERITY", "INFO")
.element("MESSAGE", "The operation succeeded.")
)
.element("DTSERVER", dtnow_string)
.element("LANGUAGE", "ENG");
Tag fi("FI");
if (!fiorg().isEmpty())
fi.element("ORG", fiorg());
if (!fiid().isEmpty())
fi.element("FID", fiid());
if (!fi.isEmpty())
sonrs.subtag(fi);
return Tag("SIGNONMSGSRSV1").subtag(sonrs);
}
MyMoneyOfxConnector::Tag MyMoneyOfxConnector::messageResponse(const QString& _msgType, const QString& _trnType, const Tag& _response)
{
return Tag(_msgType + "MSGSRSV1")
.subtag(Tag(_trnType + "TRNRS")
.element("TRNUID", uuid())
.subtag(Tag("STATUS").element("CODE", "0").element("SEVERITY", "INFO"))
.element("CLTCOOKIE", "1")
.subtag(_response));
}
MyMoneyOfxConnector::Tag MyMoneyOfxConnector::bankStatementResponse(const QDate& _dtstart) const
{
MyMoneyFile* file = MyMoneyFile::instance();
QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
QString transactionlist;
MyMoneyTransactionFilter filter;
filter.setDateFilter(_dtstart, QDate::currentDate());
filter.addAccount(m_account.id());
QList<MyMoneyTransaction> transactions = file->transactionList(filter);
QList<MyMoneyTransaction>::const_iterator it_transaction = transactions.begin();
while (it_transaction != transactions.end()) {
transactionlist += transaction(*it_transaction);
++it_transaction;
}
return messageResponse("BANK", "STMT", Tag("STMTRS")
.element("CURDEF", "USD")
.subtag(Tag("BANKACCTFROM").element("BANKID", iban()).element("ACCTID", accountnum()).element("ACCTTYPE", "CHECKING"))
.subtag(Tag("BANKTRANLIST").element("DTSTART", dtstart_string).element("DTEND", dtnow_string).data(transactionlist))
.subtag(Tag("LEDGERBAL").element("BALAMT", file->balance(m_account.id()).formatMoney(QString(), 2)).element("DTASOF", dtnow_string)));
}
MyMoneyOfxConnector::Tag MyMoneyOfxConnector::creditCardStatementResponse(const QDate& _dtstart) const
{
MyMoneyFile* file = MyMoneyFile::instance();
QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
QString transactionlist;
MyMoneyTransactionFilter filter;
filter.setDateFilter(_dtstart, QDate::currentDate());
filter.addAccount(m_account.id());
QList<MyMoneyTransaction> transactions = file->transactionList(filter);
QList<MyMoneyTransaction>::const_iterator it_transaction = transactions.begin();
while (it_transaction != transactions.end()) {
transactionlist += transaction(*it_transaction);
++it_transaction;
}
return messageResponse("CREDITCARD", "CCSTMT", Tag("CCSTMTRS")
.element("CURDEF", "USD")
.subtag(Tag("CCACCTFROM").element("ACCTID", accountnum()))
.subtag(Tag("BANKTRANLIST").element("DTSTART", dtstart_string).element("DTEND", dtnow_string).data(transactionlist))
.subtag(Tag("LEDGERBAL").element("BALAMT", file->balance(m_account.id()).formatMoney(QString(), 2)).element("DTASOF", dtnow_string)));
}
QString MyMoneyOfxConnector::investmentStatementResponse(const QDate& _dtstart) const
{
MyMoneyFile* file = MyMoneyFile::instance();
QString dtstart_string = _dtstart.toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
QString dtnow_string = QDateTime::currentDateTime().toString(Qt::ISODate).remove(QRegExp("[^0-9]"));
QString transactionlist;
MyMoneyTransactionFilter filter;
filter.setDateFilter(_dtstart, QDate::currentDate());
filter.addAccount(m_account.id());
filter.addAccount(m_account.accountList());
QList<MyMoneyTransaction> transactions = file->transactionList(filter);
QList<MyMoneyTransaction>::const_iterator it_transaction = transactions.begin();
while (it_transaction != transactions.end()) {
transactionlist += investmentTransaction(*it_transaction);
++it_transaction;
}
Tag securitylist("SECLIST");
QCStringList accountids = m_account.accountList();
QCStringList::const_iterator it_accountid = accountids.begin();
while (it_accountid != accountids.end()) {
MyMoneySecurity equity = file->security(file->account(*it_accountid).currencyId());
securitylist.subtag(Tag("STOCKINFO")
.subtag(Tag("SECINFO")
.subtag(Tag("SECID")
.element("UNIQUEID", equity.id())
.element("UNIQUEIDTYPE", "KMYMONEY"))
.element("SECNAME", equity.name())
.element("TICKER", equity.tradingSymbol())
.element("FIID", equity.id())));
++it_accountid;
}
return messageResponse("INVSTMT", "INVSTMT", Tag("INVSTMTRS")
.element("DTASOF", dtstart_string)
.element("CURDEF", "USD")
.subtag(Tag("INVACCTFROM").element("BROKERID", fiorg()).element("ACCTID", accountnum()))
.subtag(Tag("INVTRANLIST").element("DTSTART", dtstart_string).element("DTEND", dtnow_string).data(transactionlist))
)
+ Tag("SECLISTMSGSRSV1").subtag(securitylist);
}
MyMoneyOfxConnector::Tag MyMoneyOfxConnector::transaction(const MyMoneyTransaction& _t) const
{
// This method creates a transaction tag using ONLY the elements that importer uses
MyMoneyFile* file = MyMoneyFile::instance();
//Use this version for bank/cc transactions
MyMoneySplit s = _t.splitByAccount(m_account.id(), true);
//TODO (Ace) Write "investmentTransaction()"...
//Use this version for inv transactions
//MyMoneySplit s = _t.splitByAccount( m_account.accountList(), true );
Tag result("STMTTRN");
result
// This is a temporary hack. I don't use the trntype field in importing at all,
// but libofx requires it to be there in order to import the file.
.element("TRNTYPE", "DEBIT")
.element("DTPOSTED", _t.postDate().toString(Qt::ISODate).remove(QRegExp("[^0-9]")))
.element("TRNAMT", s.value().formatMoney(QString(), 2));
if (! _t.bankID().isEmpty())
result.element("FITID", _t.bankID());
else
result.element("FITID", _t.id());
if (! s.number().isEmpty())
result.element("CHECKNUM", s.number());
if (! s.payeeId().isEmpty())
result.element("NAME", file->payee(s.payeeId()).name());
if (! _t.memo().isEmpty())
result.element("MEMO", _t.memo());
return result;
}
MyMoneyOfxConnector::Tag MyMoneyOfxConnector::investmentTransaction(const MyMoneyTransaction& _t) const
{
MyMoneyFile* file = MyMoneyFile::instance();
//Use this version for inv transactions
MyMoneySplit s = _t.splitByAccount(m_account.accountList(), true);
QByteArray stockid = file->account(s.accountId()).currencyId();
Tag invtran("INVTRAN");
invtran.element("FITID", _t.id()).element("DTTRADE", _t.postDate().toString(Qt::ISODate).remove(QRegExp("[^0-9]")));
if (!_t.memo().isEmpty())
invtran.element("MEMO", _t.memo());
if (s.action() == MyMoneySplit::ActionBuyShares) {
if (s.shares().isNegative()) {
return Tag("SELLSTOCK")
.subtag(Tag("INVSELL")
.subtag(invtran)
.subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY"))
.element("UNITS", QString(((s.shares())).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
.element("UNITPRICE", QString((s.value() / s.shares()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.]")))
.element("TOTAL", QString((-s.value()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
.element("SUBACCTSEC", "CASH")
.element("SUBACCTFUND", "CASH"))
.element("SELLTYPE", "SELL");
} else {
return Tag("BUYSTOCK")
.subtag(Tag("INVBUY")
.subtag(invtran)
.subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY"))
.element("UNITS", QString((s.shares()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
.element("UNITPRICE", QString((s.value() / s.shares()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.]")))
.element("TOTAL", QString((-(s.value())).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
.element("SUBACCTSEC", "CASH")
.element("SUBACCTFUND", "CASH"))
.element("BUYTYPE", "BUY");
}
} else if (s.action() == MyMoneySplit::ActionReinvestDividend) {
// Should the TOTAL tag really be negative for a REINVEST? That's very strange, but
// it's what they look like coming from my bank, and I can't find any information to refute it.
return Tag("REINVEST")
.subtag(invtran)
.subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY"))
.element("INCOMETYPE", "DIV")
.element("TOTAL", QString((-s.value()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
.element("SUBACCTSEC", "CASH")
.element("UNITS", QString((s.shares()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.\\-]")))
.element("UNITPRICE", QString((s.value() / s.shares()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9.]")));
} else if (s.action() == MyMoneySplit::ActionDividend) {
// find the split with the category, which has the actual amount of the dividend
QList<MyMoneySplit> splits = _t.splits();
QList<MyMoneySplit>::const_iterator it_split = splits.begin();
bool found = false;
while (it_split != splits.end()) {
QByteArray accid = (*it_split).accountId();
MyMoneyAccount acc = file->account(accid);
- if (acc.accountType() == MyMoneyAccount::Income || acc.accountType() == MyMoneyAccount::Expense) {
+ if (acc.accountType() == eMyMoney::Account::Income || acc.accountType() == eMyMoney::Account::Expense) {
found = true;
break;
}
++it_split;
}
if (found)
return Tag("INCOME")
.subtag(invtran)
.subtag(Tag("SECID").element("UNIQUEID", stockid).element("UNIQUEIDTYPE", "KMYMONEY"))
.element("INCOMETYPE", "DIV")
.element("TOTAL", QString((-(*it_split).value()).formatMoney(QString(), 2)).remove(QRegExp("[^0-9\\.\\-]")))
.element("SUBACCTSEC", "CASH")
.element("SUBACCTFUND", "CASH");
else
return Tag("ERROR").element("DETAILS", "Unable to determine the amount of this income transaction.");
}
//FIXME: Do something useful with these errors
return Tag("ERROR").element("DETAILS", "This transaction contains an unsupported action type");
}
#endif
KWallet::Wallet *openSynchronousWallet()
{
using KWallet::Wallet;
// first handle the simple case in which we already use the wallet but need the object again in
// this case the wallet access permission dialog will no longer appear so we don't need to pass
// a valid window id or do anything special since the function call should return immediately
const bool alreadyUsingTheWallet = Wallet::users(Wallet::NetworkWallet()).contains("KMyMoney");
if (alreadyUsingTheWallet) {
return Wallet::openWallet(Wallet::NetworkWallet(), 0, Wallet::Synchronous);
}
// search for a suitable parent for the wallet than needs to be deactivated while the
// wallet access permission dialog is not dismissed with either accept or reject
KWallet::Wallet *wallet = 0;
QWidget *parentWidgetForWallet = 0;
if (qApp->activeModalWidget()) {
parentWidgetForWallet = qApp->activeModalWidget();
} else if (qApp->activeWindow()) {
parentWidgetForWallet = qApp->activeWindow();
} else {
QList<KMainWindow *> mainWindowList = KMainWindow::memberList();
if (!mainWindowList.isEmpty())
parentWidgetForWallet = mainWindowList.front();
}
// only open the wallet synchronously if we have a valid parent otherwise crashes could occur
if (parentWidgetForWallet) {
// while the wallet is being opened disable the widget to prevent input processing
const bool enabled = parentWidgetForWallet->isEnabled();
parentWidgetForWallet->setEnabled(false);
wallet = Wallet::openWallet(Wallet::NetworkWallet(), parentWidgetForWallet->winId(), Wallet::Synchronous);
parentWidgetForWallet->setEnabled(enabled);
}
return wallet;
}
diff --git a/kmymoney/plugins/onlinetasks/sepa/tasks/sepaonlinetransferimpl.cpp b/kmymoney/plugins/onlinetasks/sepa/tasks/sepaonlinetransferimpl.cpp
index 96900df41..7bc10a5b9 100644
--- a/kmymoney/plugins/onlinetasks/sepa/tasks/sepaonlinetransferimpl.cpp
+++ b/kmymoney/plugins/onlinetasks/sepa/tasks/sepaonlinetransferimpl.cpp
@@ -1,369 +1,370 @@
/*
This file is part of KMyMoney, A Personal Finance Manager by KDE
Copyright (C) 2013 Christian Dávid <christian-david@web.de>
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 <http://www.gnu.org/licenses/>.
*/
#include "sepaonlinetransferimpl.h"
#include <QSqlError>
#include <QSqlQuery>
#include "mymoneyutils.h"
#include "mymoney/mymoneyfile.h"
+#include "mymoneypayee.h"
#include "mymoney/onlinejobadministration.h"
#include "misc/validators.h"
#include "payeeidentifiertyped.h"
#include "tasks/sepaonlinetransfer.h"
static const unsigned short defaultTextKey = 51;
static const unsigned short defaultSubTextKey = 0;
/**
* @brief Fallback if plugin fails to create settings correctly
*/
class sepaOnlineTransferSettingsFallback : public sepaOnlineTransfer::settings
{
public:
// Limits getter
virtual int purposeMaxLines() const {
return 1;
}
virtual int purposeLineLength() const {
return 27;
}
virtual int purposeMinLength() const {
return 0;
}
virtual int recipientNameLineLength() const {
return 1;
}
virtual int recipientNameMinLength() const {
return 0;
}
virtual int payeeNameLineLength() const {
return 0;
}
virtual int payeeNameMinLength() const {
return 0;
}
virtual QString allowedChars() const {
return QString();
}
// Checker
virtual bool checkPurposeCharset(const QString&) const {
return false;
}
virtual bool checkPurposeLineLength(const QString&) const {
return false;
}
virtual validators::lengthStatus checkPurposeLength(const QString&) const {
return validators::tooLong;
}
virtual bool checkPurposeMaxLines(const QString&) const {
return false;
}
virtual validators::lengthStatus checkNameLength(const QString&) const {
return validators::tooLong;
}
virtual bool checkNameCharset(const QString&) const {
return false;
}
virtual validators::lengthStatus checkRecipientLength(const QString&) const {
return validators::tooLong;
}
virtual bool checkRecipientCharset(const QString&) const {
return false;
}
virtual int endToEndReferenceLength() const {
return 0;
}
virtual validators::lengthStatus checkEndToEndReferenceLength(const QString&) const {
return validators::tooLong;
}
virtual bool isIbanValid(const QString&) const {
return false;
}
virtual bool checkRecipientBic(const QString&) const {
return false;
}
virtual bool isBicMandatory(const QString&, const QString&) const {
return true;
}
};
sepaOnlineTransferImpl::sepaOnlineTransferImpl()
: sepaOnlineTransfer(),
_settings(QSharedPointer<const settings>()),
_originAccount(QString()),
_value(0),
_purpose(QString("")),
_endToEndReference(QString("")),
_beneficiaryAccount(payeeIdentifiers::ibanBic()),
_textKey(defaultTextKey),
_subTextKey(defaultSubTextKey)
{
}
sepaOnlineTransferImpl::sepaOnlineTransferImpl(const sepaOnlineTransferImpl& other)
: sepaOnlineTransfer(other),
_settings(other._settings),
_originAccount(other._originAccount),
_value(other._value),
_purpose(other._purpose),
_endToEndReference(other._endToEndReference),
_beneficiaryAccount(other._beneficiaryAccount),
_textKey(other._textKey),
_subTextKey(other._subTextKey)
{
}
sepaOnlineTransfer *sepaOnlineTransferImpl::clone() const
{
sepaOnlineTransfer *transfer = new sepaOnlineTransferImpl(*this);
return transfer;
}
//! @todo add validation of local name
bool sepaOnlineTransferImpl::isValid() const
{
QString iban;
try {
payeeIdentifier ident = originAccountIdentifier();
iban = ident.data<payeeIdentifiers::ibanBic>()->electronicIban();
} catch (payeeIdentifier::exception&) {
}
QSharedPointer<const sepaOnlineTransfer::settings> settings = getSettings();
if (settings->checkPurposeLength(_purpose) == validators::ok
&& settings->checkPurposeMaxLines(_purpose)
&& settings->checkPurposeLineLength(_purpose)
&& settings->checkPurposeCharset(_purpose)
&& settings->checkEndToEndReferenceLength(_endToEndReference) == validators::ok
//&& settings->checkRecipientCharset( _beneficiaryAccount.ownerName() )
//&& settings->checkRecipientLength( _beneficiaryAccount.ownerName()) == validators::ok
&& _beneficiaryAccount.isIbanValid() // do not check the BIC, maybe it is not needed
&& (!settings->isBicMandatory(iban, _beneficiaryAccount.electronicIban()) || (settings->checkRecipientBic(_beneficiaryAccount.bic()) && _beneficiaryAccount.isValid() /** @todo double check of BIC here, fix that */))
&& value().isPositive()
)
return true;
return false;
}
payeeIdentifier sepaOnlineTransferImpl::originAccountIdentifier() const
{
QList< payeeIdentifierTyped<payeeIdentifiers::ibanBic> > idents = MyMoneyFile::instance()->account(_originAccount).payeeIdentifiersByType<payeeIdentifiers::ibanBic>();
if (!idents.isEmpty()) {
payeeIdentifierTyped<payeeIdentifiers::ibanBic> ident = idents[0];
ident->setOwnerName(MyMoneyFile::instance()->user().name());
return ident;
}
return payeeIdentifier(new payeeIdentifiers::ibanBic);
}
/** @todo Return EUR */
MyMoneySecurity sepaOnlineTransferImpl::currency() const
{
#if 0
return MyMoneyFile::instance()->security(originMyMoneyAccount().currencyId());
#endif
return MyMoneyFile::instance()->baseCurrency();
}
/**
* @internal To ensure that we never return a null_ptr, @a sepaOnlineTransferSettingsFallback is used if the online plugin fails
* to give us an correct value
*/
QSharedPointer<const sepaOnlineTransfer::settings> sepaOnlineTransferImpl::getSettings() const
{
if (_settings.isNull()) {
_settings = onlineJobAdministration::instance()->taskSettings<sepaOnlineTransferImpl::settings>(name(), _originAccount);
if (_settings.isNull())
_settings = QSharedPointer< const sepaOnlineTransfer::settings >(new sepaOnlineTransferSettingsFallback);
}
Q_CHECK_PTR(_settings);
return _settings;
}
void sepaOnlineTransferImpl::setOriginAccount(const QString &accountId)
{
if (_originAccount != accountId) {
_originAccount = accountId;
_settings = QSharedPointer<const sepaOnlineTransferImpl::settings>();
}
}
void sepaOnlineTransferImpl::writeXML(QDomDocument& document, QDomElement& parent) const
{
Q_UNUSED(document);
parent.setAttribute("originAccount", _originAccount);
parent.setAttribute("value", _value.toString());
parent.setAttribute("textKey", _textKey);
parent.setAttribute("subTextKey", _subTextKey);
if (!_purpose.isEmpty()) {
parent.setAttribute("purpose", _purpose);
}
if (!_endToEndReference.isEmpty()) {
parent.setAttribute("endToEndReference", _endToEndReference);
}
QDomElement beneficiaryEl = document.createElement("beneficiary");
_beneficiaryAccount.writeXML(document, beneficiaryEl);
parent.appendChild(beneficiaryEl);
}
sepaOnlineTransfer* sepaOnlineTransferImpl::createFromXml(const QDomElement& element) const
{
sepaOnlineTransferImpl* task = new sepaOnlineTransferImpl();
task->setOriginAccount(element.attribute("originAccount", QString()));
task->setValue(MyMoneyMoney(QStringEmpty(element.attribute("value", QString()))));
task->_textKey = element.attribute("textKey", QString().setNum(defaultTextKey)).toUShort();
task->_subTextKey = element.attribute("subTextKey", QString().setNum(defaultSubTextKey)).toUShort();
task->setPurpose(element.attribute("purpose", QString()));
task->setEndToEndReference(element.attribute("endToEndReference", QString()));
payeeIdentifiers::ibanBic beneficiary;
payeeIdentifiers::ibanBic* beneficiaryPtr = 0;
QDomElement beneficiaryEl = element.firstChildElement("beneficiary");
if (!beneficiaryEl.isNull()) {
beneficiaryPtr = beneficiary.createFromXml(beneficiaryEl);
}
if (beneficiaryPtr == 0) {
task->_beneficiaryAccount = beneficiary;
} else {
task->_beneficiaryAccount = *beneficiaryPtr;
}
delete beneficiaryPtr;
return task;
}
onlineTask* sepaOnlineTransferImpl::createFromSqlDatabase(QSqlDatabase connection, const QString& onlineJobId) const
{
Q_ASSERT(!onlineJobId.isEmpty());
Q_ASSERT(connection.isOpen());
QSqlQuery query = QSqlQuery(
"SELECT originAccount, value, purpose, endToEndReference, beneficiaryName, beneficiaryIban, "
" beneficiaryBic, textKey, subTextKey FROM kmmSepaOrders WHERE id = ?",
connection
);
query.bindValue(0, onlineJobId);
if (query.exec() && query.next()) {
sepaOnlineTransferImpl* task = new sepaOnlineTransferImpl();
task->setOriginAccount(query.value(0).toString());
task->setValue(MyMoneyMoney(query.value(1).toString()));
task->setPurpose(query.value(2).toString());
task->setEndToEndReference(query.value(3).toString());
task->_textKey = query.value(7).toUInt();
task->_subTextKey = query.value(8).toUInt();
payeeIdentifiers::ibanBic beneficiary;
beneficiary.setOwnerName(query.value(4).toString());
beneficiary.setIban(query.value(5).toString());
beneficiary.setBic(query.value(6).toString());
task->_beneficiaryAccount = beneficiary;
return task;
}
return 0;
}
void sepaOnlineTransferImpl::bindValuesToQuery(QSqlQuery& query, const QString& id) const
{
query.bindValue(":id", id);
query.bindValue(":originAccount", _originAccount);
query.bindValue(":value", _value.toString());
query.bindValue(":purpose", _purpose);
query.bindValue(":endToEndReference", (_endToEndReference.isEmpty()) ? QVariant() : QVariant::fromValue(_endToEndReference));
query.bindValue(":beneficiaryName", _beneficiaryAccount.ownerName());
query.bindValue(":beneficiaryIban", _beneficiaryAccount.electronicIban());
query.bindValue(":beneficiaryBic", (_beneficiaryAccount.storedBic().isEmpty()) ? QVariant() : QVariant::fromValue(_beneficiaryAccount.storedBic()));
query.bindValue(":textKey", _textKey);
query.bindValue(":subTextKey", _subTextKey);
}
bool sepaOnlineTransferImpl::sqlSave(QSqlDatabase databaseConnection, const QString& onlineJobId) const
{
QSqlQuery query = QSqlQuery(databaseConnection);
query.prepare("INSERT INTO kmmSepaOrders ("
" id, originAccount, value, purpose, endToEndReference, beneficiaryName, beneficiaryIban, "
" beneficiaryBic, textKey, subTextKey) "
" VALUES( :id, :originAccount, :value, :purpose, :endToEndReference, :beneficiaryName, :beneficiaryIban, "
" :beneficiaryBic, :textKey, :subTextKey ) "
);
bindValuesToQuery(query, onlineJobId);
if (!query.exec()) {
qWarning("Error while saving sepa order '%s': %s", qPrintable(onlineJobId), qPrintable(query.lastError().text()));
return false;
}
return true;
}
bool sepaOnlineTransferImpl::sqlModify(QSqlDatabase databaseConnection, const QString& onlineJobId) const
{
QSqlQuery query = QSqlQuery(databaseConnection);
query.prepare(
"UPDATE kmmSepaOrders SET"
" originAccount = :originAccount,"
" value = :value,"
" purpose = :purpose,"
" endToEndReference = :endToEndReference,"
" beneficiaryName = :beneficiaryName,"
" beneficiaryIban = :beneficiaryIban,"
" beneficiaryBic = :beneficiaryBic,"
" textKey = :textKey,"
" subTextKey = :subTextKey "
" WHERE id = :id");
bindValuesToQuery(query, onlineJobId);
if (!query.exec()) {
qWarning("Could not modify sepaOnlineTransfer '%s': %s", qPrintable(onlineJobId), qPrintable(query.lastError().text()));
return false;
}
return true;
}
bool sepaOnlineTransferImpl::sqlRemove(QSqlDatabase databaseConnection, const QString& onlineJobId) const
{
QSqlQuery query = QSqlQuery(databaseConnection);
query.prepare("DELETE FROM kmmSepaOrders WHERE id = ?");
query.bindValue(0, onlineJobId);
return query.exec();
}
bool sepaOnlineTransferImpl::hasReferenceTo(const QString& id) const
{
return (id == _originAccount);
}
QString sepaOnlineTransferImpl::jobTypeName() const
{
return QLatin1String("SEPA Credit Transfer");
}
diff --git a/kmymoney/plugins/printcheck/printcheck.cpp b/kmymoney/plugins/printcheck/printcheck.cpp
index 81dcb27b5..9d407f00e 100644
--- a/kmymoney/plugins/printcheck/printcheck.cpp
+++ b/kmymoney/plugins/printcheck/printcheck.cpp
@@ -1,238 +1,238 @@
/***************************************************************************
* Copyright 2009 Cristian Onet onet.cristian@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) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 of the license. *
* *
* 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 <http://www.gnu.org/licenses/> *
***************************************************************************/
#include "printcheck.h"
#include "config-kmymoney.h"
// QT includes
#include <QAction>
#include <QFile>
#include <QDialog>
#ifdef ENABLE_WEBENGINE
#include <QWebEngineView>
#else
#include <KDEWebKit/KWebView>
#endif
#include <QtPrintSupport/QPrintDialog>
#include <QtPrintSupport/QPrinter>
// KDE includes
#include <KActionCollection>
#include <KPluginInfo>
#include <QStandardPaths>
#include <KLocalizedString>
// KMyMoney includes
#include "mymoneyfile.h"
#include "mymoneyaccount.h"
#include "mymoneyinstitution.h"
#include "mymoneymoney.h"
#include "mymoneypayee.h"
#include "mymoneysecurity.h"
#include "mymoneysplit.h"
#include "mymoneytransaction.h"
#include "mymoneyutils.h"
#include "viewinterface.h"
#include "numbertowords.h"
#include "pluginsettings.h"
struct KMMPrintCheckPlugin::Private {
QAction* m_action;
QString m_checkTemplateHTML;
QStringList m_printedTransactionIdList;
KMyMoneyRegister::SelectedTransactions m_transactions;
};
KMMPrintCheckPlugin::KMMPrintCheckPlugin()
: KMyMoneyPlugin::Plugin(nullptr, "Print check"/*must be the same as X-KDE-PluginInfo-Name*/)
{
// Tell the host application to load my GUI component
setComponentName("kmm_printcheck", i18n("Print check"));
setXMLFile("kmm_printcheck.rc");
// For ease announce that we have been loaded.
qDebug("KMyMoney printcheck plugin loaded");
d = std::unique_ptr<Private>(new Private);
// Create the actions of this plugin
QString actionName = i18n("Print check");
d->m_action = actionCollection()->addAction("transaction_printcheck", this, SLOT(slotPrintCheck()));
d->m_action->setText(actionName);
// wait until a transaction is selected before enableing the action
d->m_action->setEnabled(false);
d->m_printedTransactionIdList = PluginSettings::printedChecks();
readCheckTemplate();
//! @todo Christian: Replace
#if 0
connect(KMyMoneyPlugin::PluginLoader::instance(), SIGNAL(plug(KPluginInfo*)), this, SLOT(slotPlug(KPluginInfo*)));
connect(KMyMoneyPlugin::PluginLoader::instance(), SIGNAL(configChanged(Plugin*)), this, SLOT(slotUpdateConfig()));
#endif
}
/**
* @internal Destructor is needed because destructor call of unique_ptr must be in this compile unit
*/
KMMPrintCheckPlugin::~KMMPrintCheckPlugin()
{
}
void KMMPrintCheckPlugin::readCheckTemplate()
{
QString checkTemplateHTMLPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kmm_printcheck/check_template.html");
if (PluginSettings::checkTemplateFile().isEmpty()) {
PluginSettings::setCheckTemplateFile(checkTemplateHTMLPath);
PluginSettings::self()->save();
}
QFile checkTemplateHTMLFile(PluginSettings::checkTemplateFile());
checkTemplateHTMLFile.open(QIODevice::ReadOnly);
QTextStream stream(&checkTemplateHTMLFile);
d->m_checkTemplateHTML = stream.readAll();
checkTemplateHTMLFile.close();
}
bool KMMPrintCheckPlugin::canBePrinted(const KMyMoneyRegister::SelectedTransaction & selectedTransaction) const
{
MyMoneyFile* file = MyMoneyFile::instance();
- bool isACheck = file->account(selectedTransaction.split().accountId()).accountType() == MyMoneyAccount::Checkings && selectedTransaction.split().shares().isNegative();
+ bool isACheck = file->account(selectedTransaction.split().accountId()).accountType() == eMyMoney::Account::Checkings && selectedTransaction.split().shares().isNegative();
return isACheck && d->m_printedTransactionIdList.contains(selectedTransaction.transaction().id()) == 0;
}
void KMMPrintCheckPlugin::markAsPrinted(const KMyMoneyRegister::SelectedTransaction & selectedTransaction)
{
d->m_printedTransactionIdList.append(selectedTransaction.transaction().id());
}
void KMMPrintCheckPlugin::slotPrintCheck()
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyMoneyToWordsConverter converter;
#ifdef ENABLE_WEBENGINE
auto htmlPart = new QWebEngineView();
#else
auto htmlPart = new KWebView();
#endif
KMyMoneyRegister::SelectedTransactions::const_iterator it;
for (it = d->m_transactions.constBegin(); it != d->m_transactions.constEnd(); ++it) {
if (!canBePrinted(*it))
continue; // skip this check since it was already printed
QString checkHTML = d->m_checkTemplateHTML;
const MyMoneyAccount account = file->account((*it).split().accountId());
const MyMoneySecurity currency = file->currency(account.currencyId());
const MyMoneyInstitution institution = file->institution(file->account((*it).split().accountId()).institutionId());
// replace the predefined tokens
// data about the user
checkHTML.replace("$OWNER_NAME", file->user().name());
checkHTML.replace("$OWNER_ADDRESS", file->user().address());
checkHTML.replace("$OWNER_CITY", file->user().city());
checkHTML.replace("$OWNER_STATE", file->user().state());
// data about the account institution
checkHTML.replace("$INSTITUTION_NAME", institution.name());
checkHTML.replace("$INSTITUTION_STREET", institution.street());
checkHTML.replace("$INSTITUTION_TELEPHONE", institution.telephone());
checkHTML.replace("$INSTITUTION_TOWN", institution.town());
checkHTML.replace("$INSTITUTION_CITY", institution.city());
checkHTML.replace("$INSTITUTION_POSTCODE", institution.postcode());
checkHTML.replace("$INSTITUTION_MANAGER", institution.manager());
// data about the transaction
checkHTML.replace("$DATE", QLocale().toString((*it).transaction().postDate(), QLocale::ShortFormat));
checkHTML.replace("$CHECK_NUMBER", (*it).split().number());
checkHTML.replace("$PAYEE_NAME", file->payee((*it).split().payeeId()).name());
checkHTML.replace("$PAYEE_ADDRESS", file->payee((*it).split().payeeId()).address());
checkHTML.replace("$PAYEE_CITY", file->payee((*it).split().payeeId()).city());
checkHTML.replace("$PAYEE_POSTCODE", file->payee((*it).split().payeeId()).postcode());
checkHTML.replace("$PAYEE_STATE", file->payee((*it).split().payeeId()).state());
checkHTML.replace("$AMOUNT_STRING", converter.convert((*it).split().shares().abs(), currency.smallestAccountFraction()));
checkHTML.replace("$AMOUNT_DECIMAL", MyMoneyUtils::formatMoney((*it).split().shares().abs(), currency));
checkHTML.replace("$MEMO", (*it).split().memo());
// print the check
htmlPart->setHtml(checkHTML, QUrl("file://"));
m_currentPrinter = new QPrinter();
QPointer<QPrintDialog> dialog = new QPrintDialog(m_currentPrinter);
dialog->setWindowTitle(QString());
if (dialog->exec() != QDialog::Accepted) {
delete m_currentPrinter;
m_currentPrinter = nullptr;
continue;
} else {
#ifdef ENABLE_WEBENGINE
htmlPart->page()->print(m_currentPrinter, [=] (bool) {delete m_currentPrinter; m_currentPrinter = nullptr;});
#else
htmlPart->print(m_currentPrinter);
#endif
}
delete dialog;
// mark the transaction as printed
markAsPrinted(*it);
}
PluginSettings::setPrintedChecks(d->m_printedTransactionIdList);
delete htmlPart;
}
void KMMPrintCheckPlugin::slotTransactionsSelected(const KMyMoneyRegister::SelectedTransactions& transactions)
{
d->m_transactions = transactions;
bool actionEnabled = false;
// enable/disable the action depending if there are transactions selected or not
// and whether they can be printed or not
KMyMoneyRegister::SelectedTransactions::const_iterator it;
for (it = d->m_transactions.constBegin(); it != d->m_transactions.constEnd(); ++it) {
if (canBePrinted(*it)) {
actionEnabled = true;
break;
}
}
d->m_action->setEnabled(actionEnabled);
}
// the plugin loader plugs in a plugin
void KMMPrintCheckPlugin::slotPlug(KPluginInfo *info)
{
if (info->name() == objectName()) {
connect(viewInterface(), SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)),
this, SLOT(slotTransactionsSelected(KMyMoneyRegister::SelectedTransactions)));
}
}
// the plugin's configurations has changed
void KMMPrintCheckPlugin::slotUpdateConfig()
{
PluginSettings::self()->load();
// re-read the data because the configuration has changed
readCheckTemplate();
d->m_printedTransactionIdList = PluginSettings::printedChecks();
}
diff --git a/kmymoney/plugins/qif/export/kexportdlg.cpp b/kmymoney/plugins/qif/export/kexportdlg.cpp
index e4c73b65d..1dae6f1f9 100644
--- a/kmymoney/plugins/qif/export/kexportdlg.cpp
+++ b/kmymoney/plugins/qif/export/kexportdlg.cpp
@@ -1,224 +1,225 @@
/***************************************************************************
kexportdlg.cpp - description
-------------------
begin : Tue May 22 2001
copyright : (C) 2001 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@ctv.es>
Felix Rodriguez <frodriguez@mail.wesleyan.edu>
***************************************************************************/
/***************************************************************************
* *
* 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 "kexportdlg.h"
// ----------------------------------------------------------------------------
// QT Headers
#include <QLabel>
#include <QPixmap>
#include <QList>
#include <QUrl>
#include <QPushButton>
#include <QIcon>
#include <QFileDialog>
// ----------------------------------------------------------------------------
// KDE Headers
#include <kmessagebox.h>
#include <KConfigGroup>
#include <KGuiItem>
#include <KStandardGuiItem>
#include <KSharedConfig>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Headers
#include "mymoneycategory.h"
#include "mymoneyfile.h"
+#include "mymoneytransactionfilter.h"
#include "kmymoneyaccountcombo.h"
#include "kmymoneyutils.h"
#include "models.h"
#include "accountsmodel.h"
#include <icons/icons.h>
using namespace Icons;
KExportDlg::KExportDlg(QWidget *parent)
: KExportDlgDecl(parent)
{
// Set (almost) all the last used options
readConfig();
loadProfiles(true);
loadAccounts();
// load button icons
KGuiItem::assign(m_qbuttonCancel, KStandardGuiItem::cancel());
KGuiItem okButtenItem(i18n("&Export"),
QIcon::fromTheme(g_Icons[Icon::DocumentExport]),
i18n("Start operation"),
i18n("Use this to start the export operation"));
KGuiItem::assign(m_qbuttonOk, okButtenItem);
KGuiItem browseButtenItem(i18n("&Browse..."),
QIcon::fromTheme(g_Icons[Icon::DocumentOpen]),
i18n("Select filename"),
i18n("Use this to select a filename to export to"));
KGuiItem::assign(m_qbuttonBrowse, browseButtenItem);
// connect the buttons to their functionality
connect(m_qbuttonBrowse, &QAbstractButton::clicked, this, &KExportDlg::slotBrowse);
connect(m_qbuttonOk, &QAbstractButton::clicked, this, &KExportDlg::slotOkClicked);
connect(m_qbuttonCancel, &QAbstractButton::clicked, this, &QDialog::reject);
// connect the change signals to the check slot and perform initial check
connect(m_qlineeditFile, SIGNAL(textChanged(QString)), this, SLOT(checkData()));
connect(m_qcheckboxAccount, SIGNAL(toggled(bool)), this, SLOT(checkData()));
connect(m_qcheckboxCategories, SIGNAL(toggled(bool)), this, SLOT(checkData()));
connect(m_accountComboBox, SIGNAL(accountSelected(QString)), this, SLOT(checkData(QString)));
connect(m_profileComboBox, SIGNAL(activated(int)), this, SLOT(checkData()));
connect(m_kmymoneydateStart, SIGNAL(dateChanged(QDate)), this, SLOT(checkData()));
connect(m_kmymoneydateEnd, SIGNAL(dateChanged(QDate)), this, SLOT(checkData()));
checkData(QString());
}
KExportDlg::~KExportDlg()
{
}
void KExportDlg::slotBrowse()
{
auto newName(QFileDialog::getSaveFileName(this, QString(), QString(), QLatin1String("*.QIF")));
if (!newName.endsWith(QLatin1String(".qif"), Qt::CaseInsensitive))
newName.append(QLatin1String(".qif"));
if (!newName.isEmpty())
m_qlineeditFile->setText(newName);
}
void KExportDlg::loadProfiles(const bool selectLast)
{
QString current = m_profileComboBox->currentText();
m_profileComboBox->clear();
QStringList list;
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup grp = config->group("Profiles");
list = grp.readEntry("profiles", QStringList());
list.sort();
m_profileComboBox->insertItems(0, list);
if (selectLast == true) {
grp = config->group("Last Use Settings");
current = grp.readEntry("KExportDlg_LastProfile");
}
m_profileComboBox->setCurrentItem(0);
if (list.contains(current) > 0)
m_profileComboBox->setCurrentIndex(m_profileComboBox->findText(current, Qt::MatchExactly));
}
void KExportDlg::slotOkClicked()
{
// Make sure we save the last used settings for use next time,
writeConfig();
accept();
}
void KExportDlg::readConfig()
{
KSharedConfigPtr kconfig = KSharedConfig::openConfig();
KConfigGroup kgrp = kconfig->group("Last Use Settings");
m_qlineeditFile->setText(kgrp.readEntry("KExportDlg_LastFile"));
m_qcheckboxAccount->setChecked(kgrp.readEntry("KExportDlg_AccountOpt", true));
m_qcheckboxCategories->setChecked(kgrp.readEntry("KExportDlg_CatOpt", true));
m_kmymoneydateStart->setDate(kgrp.readEntry("KExportDlg_StartDate", QDate()));
m_kmymoneydateEnd->setDate(kgrp.readEntry("KExportDlg_EndDate", QDate()));
// m_profileComboBox is loaded in loadProfiles(), so we don't worry here
// m_accountComboBox is loaded in loadAccounts(), so we don't worry here
}
void KExportDlg::writeConfig()
{
KSharedConfigPtr kconfig = KSharedConfig::openConfig();
KConfigGroup grp = kconfig->group("Last Use Settings");
grp.writeEntry("KExportDlg_LastFile", m_qlineeditFile->text());
grp.writeEntry("KExportDlg_AccountOpt", m_qcheckboxAccount->isChecked());
grp.writeEntry("KExportDlg_CatOpt", m_qcheckboxCategories->isChecked());
grp.writeEntry("KExportDlg_StartDate", QDateTime(m_kmymoneydateStart->date()));
grp.writeEntry("KExportDlg_EndDate", QDateTime(m_kmymoneydateEnd->date()));
grp.writeEntry("KExportDlg_LastProfile", m_profileComboBox->currentText());
kconfig->sync();
}
void KExportDlg::checkData(const QString& accountId)
{
bool okEnabled = false;
if (!m_qlineeditFile->text().isEmpty()) {
auto strFile(m_qlineeditFile->text());
if (!strFile.endsWith(QLatin1String(".qif"), Qt::CaseInsensitive))
strFile.append(QLatin1String(".qif"));
m_qlineeditFile->setText(strFile);
}
MyMoneyAccount account;
if (!accountId.isEmpty()) {
MyMoneyFile* file = MyMoneyFile::instance();
account = file->account(accountId);
if (m_lastAccount != accountId) {
MyMoneyTransactionFilter filter(accountId);
QList<MyMoneyTransaction> list = file->transactionList(filter);
QList<MyMoneyTransaction>::Iterator it;
if (!list.isEmpty()) {
it = list.begin();
m_kmymoneydateStart->loadDate((*it).postDate());
it = list.end();
--it;
m_kmymoneydateEnd->loadDate((*it).postDate());
}
m_lastAccount = accountId;
m_accountComboBox->setSelected(account.id());
}
}
if (!m_qlineeditFile->text().isEmpty()
&& !m_accountComboBox->getSelected().isEmpty()
&& !m_profileComboBox->currentText().isEmpty()
&& m_kmymoneydateStart->date() <= m_kmymoneydateEnd->date()
&& (m_qcheckboxAccount->isChecked() || m_qcheckboxCategories->isChecked()))
okEnabled = true;
m_qbuttonOk->setEnabled(okEnabled);
}
void KExportDlg::loadAccounts()
{
auto filterProxyModel = new AccountNamesFilterProxyModel(this);
- filterProxyModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Asset, MyMoneyAccount::Liability});
+ filterProxyModel->addAccountGroup(QVector<eMyMoney::Account> {eMyMoney::Account::Asset, eMyMoney::Account::Liability});
auto const model = Models::instance()->accountsModel();
model->load();
filterProxyModel->setSourceColumns(model->getColumns());
filterProxyModel->setSourceModel(model);
filterProxyModel->sort((int)eAccountsModel::Column::Account);
m_accountComboBox->setModel(filterProxyModel);
}
QString KExportDlg::accountId() const
{
return m_lastAccount;
}
diff --git a/kmymoney/plugins/qif/export/mymoneyqifwriter.cpp b/kmymoney/plugins/qif/export/mymoneyqifwriter.cpp
index da796a302..e15f54b6c 100644
--- a/kmymoney/plugins/qif/export/mymoneyqifwriter.cpp
+++ b/kmymoney/plugins/qif/export/mymoneyqifwriter.cpp
@@ -1,463 +1,470 @@
/***************************************************************************
mymoneyqifwriter.cpp - description
-------------------
begin : Sun Jan 5 2003
copyright : (C) 2000-2003 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@users.sourceforge.net>
Allan Anderson agander93@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 "mymoneyqifwriter.h"
// ----------------------------------------------------------------------------
// QT Headers
#include <QFile>
#include <QList>
#include <QDebug>
// ----------------------------------------------------------------------------
// KDE Headers
#include <kmessagebox.h>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Headers
#include "mymoneyfile.h"
+#include "mymoneyaccount.h"
+#include "mymoneytransaction.h"
+#include "mymoneytransactionfilter.h"
+#include "mymoneysplit.h"
+#include "mymoneymoney.h"
+#include "mymoneypayee.h"
+#include "mymoneyexception.h"
MyMoneyQifWriter::MyMoneyQifWriter()
{
}
MyMoneyQifWriter::~MyMoneyQifWriter()
{
}
void MyMoneyQifWriter::write(const QString& filename, const QString& profile,
const QString& accountId, const bool accountData,
const bool categoryData,
const QDate& startDate, const QDate& endDate)
{
m_qifProfile.loadProfile("Profile-" + profile);
QFile qifFile(filename);
if (qifFile.open(QIODevice::WriteOnly)) {
QTextStream s(&qifFile);
s.setCodec("UTF-8");
try {
if (categoryData) {
writeCategoryEntries(s);
}
if (accountData) {
writeAccountEntry(s, accountId, startDate, endDate);
}
emit signalProgress(-1, -1);
} catch (const MyMoneyException &e) {
QString errMsg = i18n("Unexpected exception '%1' thrown in %2, line %3 "
"caught in MyMoneyQifWriter::write()", e.what(), e.file(), e.line());
KMessageBox::error(0, errMsg);
}
qifFile.close();
qDebug() << "Export completed.\n";
} else {
KMessageBox::error(0, i18n("Unable to open file '%1' for writing", filename));
}
}
void MyMoneyQifWriter::writeAccountEntry(QTextStream& s, const QString& accountId, const QDate& startDate, const QDate& endDate)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount account;
account = file->account(accountId);
MyMoneyTransactionFilter filter(accountId);
QString openingBalanceTransactionId;
QString type = m_qifProfile.profileType();
s << "!Type:" << type << endl;
if (type == "Invst") {
extractInvestmentEntries(s, accountId, startDate, endDate);
} else {
filter.setDateFilter(startDate, endDate);
QList<MyMoneyTransaction> list = file->transactionList(filter);
if (!startDate.isValid() || startDate <= account.openingDate()) {
s << "D" << m_qifProfile.date(account.openingDate()) << endl;
openingBalanceTransactionId = file->openingBalanceTransaction(account);
MyMoneySplit split;
if (!openingBalanceTransactionId.isEmpty()) {
MyMoneyTransaction openingBalanceTransaction = file->transaction(openingBalanceTransactionId);
split = openingBalanceTransaction.splitByAccount(account.id(), true /* match */);
}
s << "T" << m_qifProfile.value('T', split.value()) << endl;
} else {
s << "D" << m_qifProfile.date(startDate) << endl;
s << "T" << m_qifProfile.value('T', file->balance(accountId, startDate.addDays(-1))) << endl;
}
s << "CX" << endl;
s << "P" << m_qifProfile.openingBalanceText() << endl;
s << "L";
if (m_qifProfile.accountDelimiter().length())
s << m_qifProfile.accountDelimiter()[0];
s << account.name();
if (m_qifProfile.accountDelimiter().length() > 1)
s << m_qifProfile.accountDelimiter()[1];
s << endl;
s << "^" << endl;
QList<MyMoneyTransaction>::ConstIterator it;
signalProgress(0, list.count());
int count = 0;
for (it = list.constBegin(); it != list.constEnd(); ++it) {
// don't include the openingBalanceTransaction again
if ((*it).id() != openingBalanceTransactionId)
writeTransactionEntry(s, *it, accountId);
signalProgress(++count, 0);
}
}
}
void MyMoneyQifWriter::writeCategoryEntries(QTextStream &s)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount income;
MyMoneyAccount expense;
income = file->income();
expense = file->expense();
s << "!Type:Cat" << endl;
QStringList list = income.accountList() + expense.accountList();
emit signalProgress(0, list.count());
QStringList::Iterator it;
int count = 0;
for (it = list.begin(); it != list.end(); ++it) {
writeCategoryEntry(s, *it, "");
emit signalProgress(++count, 0);
}
}
void MyMoneyQifWriter::writeCategoryEntry(QTextStream &s, const QString& accountId, const QString& leadIn)
{
MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId);
QString name = acc.name();
s << "N" << leadIn << name << endl;
- s << (acc.accountGroup() == MyMoneyAccount::Expense ? "E" : "I") << endl;
+ s << (acc.accountGroup() == eMyMoney::Account::Expense ? "E" : "I") << endl;
s << "^" << endl;
QStringList list = acc.accountList();
QStringList::Iterator it;
name += ':';
for (it = list.begin(); it != list.end(); ++it) {
writeCategoryEntry(s, *it, name);
}
}
void MyMoneyQifWriter::writeTransactionEntry(QTextStream &s, const MyMoneyTransaction& t, const QString& accountId)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneySplit split = t.splitByAccount(accountId);
s << "D" << m_qifProfile.date(t.postDate()) << endl;
switch (split.reconcileFlag()) {
case MyMoneySplit::Cleared:
s << "C*" << endl;
break;
case MyMoneySplit::Reconciled:
case MyMoneySplit::Frozen:
s << "CX" << endl;
break;
default:
break;
}
if (split.memo().length() > 0) {
QString m = split.memo();
m.replace('\n', "\\n");
s << "M" << m << endl;
}
s << "T" << m_qifProfile.value('T', split.value()) << endl;
if (split.number().length() > 0)
s << "N" << split.number() << endl;
if (!split.payeeId().isEmpty()) {
MyMoneyPayee payee = file->payee(split.payeeId());
s << "P" << payee.name() << endl;
}
QList<MyMoneySplit> list = t.splits();
if (list.count() > 1) {
MyMoneySplit sp = t.splitByAccount(accountId, false);
MyMoneyAccount acc = file->account(sp.accountId());
- if (acc.accountGroup() != MyMoneyAccount::Income
- && acc.accountGroup() != MyMoneyAccount::Expense) {
+ if (acc.accountGroup() != eMyMoney::Account::Income
+ && acc.accountGroup() != eMyMoney::Account::Expense) {
s << "L" << m_qifProfile.accountDelimiter()[0]
<< MyMoneyFile::instance()->accountToCategory(sp.accountId())
<< m_qifProfile.accountDelimiter()[1] << endl;
} else {
s << "L" << file->accountToCategory(sp.accountId()) << endl;
}
if (list.count() > 2) {
QList<MyMoneySplit>::ConstIterator it;
for (it = list.constBegin(); it != list.constEnd(); ++it) {
if (!((*it) == split)) {
writeSplitEntry(s, *it);
}
}
}
}
s << "^" << endl;
}
void MyMoneyQifWriter::writeSplitEntry(QTextStream& s, const MyMoneySplit& split)
{
MyMoneyFile* file = MyMoneyFile::instance();
s << "S";
MyMoneyAccount acc = file->account(split.accountId());
- if (acc.accountGroup() != MyMoneyAccount::Income
- && acc.accountGroup() != MyMoneyAccount::Expense) {
+ if (acc.accountGroup() != eMyMoney::Account::Income
+ && acc.accountGroup() != eMyMoney::Account::Expense) {
s << m_qifProfile.accountDelimiter()[0]
<< file->accountToCategory(split.accountId())
<< m_qifProfile.accountDelimiter()[1];
} else {
s << file->accountToCategory(split.accountId());
}
s << endl;
if (split.memo().length() > 0) {
QString m = split.memo();
m.replace('\n', "\\n");
s << "E" << m << endl;
}
s << "$" << m_qifProfile.value('$', -split.value()) << endl;
}
void MyMoneyQifWriter::extractInvestmentEntries(QTextStream &s, const QString& accountId, const QDate& startDate, const QDate& endDate)
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<QString> accList = file->account(accountId).accountList();
QList<QString>::ConstIterator itAcc;
for (itAcc = accList.constBegin(); itAcc != accList.constEnd(); ++itAcc) {
MyMoneyTransactionFilter filter((*itAcc));
filter.setDateFilter(startDate, endDate);
QList<MyMoneyTransaction> list = file->transactionList(filter);
QList<MyMoneyTransaction>::ConstIterator it;
signalProgress(0, list.count());
int count = 0;
for (it = list.constBegin(); it != list.constEnd(); ++it) {
writeInvestmentEntry(s, *it, ++count);
signalProgress(count, 0);
}
}
}
void MyMoneyQifWriter::writeInvestmentEntry(QTextStream& stream, const MyMoneyTransaction& t, const int count)
{
QString s;
QString memo;
MyMoneyFile* file = MyMoneyFile::instance();
QString chkAccnt;
bool isXfer = false;
bool noError = true;
QList<MyMoneySplit> lst = t.splits();
QList<MyMoneySplit>::Iterator it;
- MyMoneyAccount::_accountTypeE typ;
+ eMyMoney::Account typ;
QString chkAccntId;
MyMoneyMoney qty;
MyMoneyMoney value;
- QMap<MyMoneyAccount::_accountTypeE, QString> map;
+ QMap<eMyMoney::Account, QString> map;
for (int i = 0; i < lst.count(); i++) {
QString actionType = lst[i].action();
MyMoneyAccount acc = file->account(lst[i].accountId());
QString accName = acc.name();
typ = acc.accountType();
map.insert(typ, lst[i].accountId());
- if (typ == MyMoneyAccount::Stock) {
+ if (typ == eMyMoney::Account::Stock) {
memo = lst[i].memo();
}
}
//
// Add date.
//
if (noError) {
s += 'D' + m_qifProfile.date(t.postDate()) + '\n';
}
for (it = lst.begin(); it != lst.end(); ++it) {
QString accName;
QString actionType = (*it).action();
MyMoneyAccount acc = file->account((*it).accountId());
typ = acc.accountType();
//
- // MyMoneyAccount::Checkings.
+ // eMyMoney::Account::Checkings.
//
- if ((acc.accountType() == MyMoneyAccount::Checkings) || (acc.accountType() == MyMoneyAccount::Cash)) {
+ if ((acc.accountType() == eMyMoney::Account::Checkings) || (acc.accountType() == eMyMoney::Account::Cash)) {
chkAccntId = (*it).accountId();
chkAccnt = file->account(chkAccntId).name();
- } else if (acc.accountType() == MyMoneyAccount::Income) {
+ } else if (acc.accountType() == eMyMoney::Account::Income) {
//
- // MyMoneyAccount::Income.
+ // eMyMoney::Account::Income.
//
- } else if (acc.accountType() == MyMoneyAccount::Expense) {
+ } else if (acc.accountType() == eMyMoney::Account::Expense) {
//
- // MyMoneyAccount::Expense.
+ // eMyMoney::Account::Expense.
//
- } else if (acc.accountType() == MyMoneyAccount::Stock) {
+ } else if (acc.accountType() == eMyMoney::Account::Stock) {
//
- // MyMoneyAccount::Stock.
+ // eMyMoney::Account::Stock.
//
qty = (*it).shares();
value = (*it).value();
accName = acc.name();
if ((actionType == "Dividend") || (actionType == "Buy") || (actionType == "IntIncome")) {
isXfer = true;
}
//
// Actions.
//
QString action;
if ((*it).action() == "Dividend") {
action = "DivX";
} else if ((*it).action() == "IntIncome") {
action = "IntIncX";
}
if ((action == "DivX") || (action == "IntIncX")) {
- if (map.value(MyMoneyAccount::Checkings).isEmpty()) {
+ if (map.value(eMyMoney::Account::Checkings).isEmpty()) {
KMessageBox::sorry(0,
QString("<qt>%1</qt>").arg(i18n("Transaction number <b>%1</b> is missing an account assignment.\nTransaction dropped.", count)),
i18n("Invalid transaction"));
noError = false;
return;
}
- MyMoneySplit sp = t.splitByAccount(map.value(MyMoneyAccount::Checkings), true);
+ MyMoneySplit sp = t.splitByAccount(map.value(eMyMoney::Account::Checkings), true);
QString txt = sp.value().formatMoney("", 2);
if (noError) {
s += 'T' + txt + '\n';
}
} else if ((*it).action() == "Buy") {
if (qty.isNegative()) {
action = "Sell";
} else {
action = "Buy";
}
} else if ((*it).action() == "Add") {
qty = (*it).shares();
if (qty.isNegative()) {
action = "Shrsout";
} else {
action = "Shrsin";
}
} else if ((*it).action() == "Reinvest") {
action = "ReinvDiv";
} else {
action = (*it).action();
}
//
// Add action.
//
if (noError) {
s += 'N' + action + '\n';
}
QString txt;
if ((action == "Buy") || (action == "Sell") || (action == "ReinvDiv")) {
//
// Add total.
//
txt = value.formatMoney("", 2);
if (action == "Sell") {
value = -value;
txt = value.formatMoney("", 2);
}
if (noError) {
s += 'T' + txt + '\n';
}
//
// Add price.
//
txt = (*it).price().formatMoney("", 6);
if (noError) {
s += 'I' + txt + '\n';
}
if (!qty.isZero()) {
//
// Add quantity.
//
if (noError) {
if (action == "Sell") {
qty = -qty;
}
s += 'Q' + m_qifProfile.value('Q', qty) + '\n';
}
}
} else if ((action == "Shrsin") || (action == "Shrsout")) {
//
// Add quantity for "Shrsin" || "Shrsout".
//
if (noError) {
if (action == "Shrsout") {
qty = -qty;
}
s += 'Q' + m_qifProfile.value('Q', qty) + '\n';
}
}
}
if (!accName.isEmpty()) {
if (noError) {
s += 'Y' + accName + '\n';
}
}
}
if (!memo.isEmpty()) {
if (noError) {
memo.replace('\n', "\\n");
s += 'M' + memo + '\n';
}
}
if ((!chkAccnt.isEmpty()) && isXfer) {
//
// Add account - including its hierarchy.
//
if (noError) {
s += 'L' + m_qifProfile.accountDelimiter()[0] + file->accountToCategory(chkAccntId)
+ m_qifProfile.accountDelimiter()[1] + '\n';
stream << s;
} else {
// Don't output the transaction
}
} else {
stream << s;
}
stream << '^' << '\n';
}
diff --git a/kmymoney/plugins/qif/import/mymoneyqifreader.cpp b/kmymoney/plugins/qif/import/mymoneyqifreader.cpp
index dec0085ef..24f21a0df 100644
--- a/kmymoney/plugins/qif/import/mymoneyqifreader.cpp
+++ b/kmymoney/plugins/qif/import/mymoneyqifreader.cpp
@@ -1,2070 +1,2071 @@
/***************************************************************************
mymoneyqifreader.cpp
-------------------
begin : Mon Jan 27 2003
copyright : (C) 2000-2003 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@users.sourceforge.net>
Ace Jones <acejones@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 "mymoneyqifreader.h"
// ----------------------------------------------------------------------------
// QT Headers
#include <QFile>
#include <QStringList>
#include <QTimer>
#include <QRegExp>
#include <QBuffer>
#include <QByteArray>
#include <QInputDialog>
#include <QDir>
// ----------------------------------------------------------------------------
// KDE Headers
#include <kmessagebox.h>
#include <kconfig.h>
#include <KConfigGroup>
#include <KSharedConfig>
#include <KLocalizedString>
#include "kjobwidgets.h"
#include "kio/job.h"
// ----------------------------------------------------------------------------
// Project Headers
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
#include "kmymoneyglobalsettings.h"
#include "mymoneystatement.h"
// define this to debug the code. Using external filters
// while debugging did not work too good for me, so I added
// this code.
// #define DEBUG_IMPORT
#ifdef DEBUG_IMPORT
#ifdef __GNUC__
#warning "DEBUG_IMPORT defined --> external filter not available!!!!!!!"
#endif
#endif
class MyMoneyQifReader::Private
{
public:
Private() :
- accountType(MyMoneyAccount::Checkings),
+ accountType(eMyMoney::Account::Checkings),
mapCategories(true) {}
- const QString accountTypeToQif(MyMoneyAccount::accountTypeE type) const;
+ const QString accountTypeToQif(eMyMoney::Account type) const;
/**
* finalize the current statement and add it to the statement list
*/
void finishStatement();
bool isTransfer(QString& name, const QString& leftDelim, const QString& rightDelim);
/**
* Converts the QIF specific N-record of investment transactions into
* a category name
*/
const QString typeToAccountName(const QString& type) const;
/**
* Converts the QIF reconcile state to the KMyMoney reconcile state
*/
MyMoneySplit::reconcileFlagE reconcileState(const QString& state) const;
/**
*/
void fixMultiLineMemo(QString& memo) const;
public:
/**
* the statement that is currently collected/processed
*/
MyMoneyStatement st;
/**
* the list of all statements to be sent to MyMoneyStatementReader
*/
QList<MyMoneyStatement> statements;
/**
* a list of already used hashes in this file
*/
QMap<QString, bool> m_hashMap;
QString st_AccountName;
QString st_AccountId;
- MyMoneyAccount::accountTypeE accountType;
+ eMyMoney::Account accountType;
bool firstTransaction;
bool mapCategories;
MyMoneyQifReader::QifEntryTypeE transactionType;
};
void MyMoneyQifReader::Private::fixMultiLineMemo(QString& memo) const
{
memo.replace("\\n", "\n");
}
void MyMoneyQifReader::Private::finishStatement()
{
// in case we have collected any data in the statement, we keep it
if ((st.m_listTransactions.count() + st.m_listPrices.count() + st.m_listSecurities.count()) > 0) {
statements += st;
qDebug("Statement with %d transactions, %d prices and %d securities added to the statement list",
st.m_listTransactions.count(), st.m_listPrices.count(), st.m_listSecurities.count());
}
MyMoneyStatement::EType type = st.m_eType; //stash type and...
// start with a fresh statement
st = MyMoneyStatement();
st.m_skipCategoryMatching = !mapCategories;
st.m_eType = type;
}
-const QString MyMoneyQifReader::Private::accountTypeToQif(MyMoneyAccount::accountTypeE type) const
+const QString MyMoneyQifReader::Private::accountTypeToQif(eMyMoney::Account type) const
{
QString rc = "Bank";
switch (type) {
default:
break;
- case MyMoneyAccount::Cash:
+ case eMyMoney::Account::Cash:
rc = "Cash";
break;
- case MyMoneyAccount::CreditCard:
+ case eMyMoney::Account::CreditCard:
rc = "CCard";
break;
- case MyMoneyAccount::Asset:
+ case eMyMoney::Account::Asset:
rc = "Oth A";
break;
- case MyMoneyAccount::Liability:
+ case eMyMoney::Account::Liability:
rc = "Oth L";
break;
- case MyMoneyAccount::Investment:
+ case eMyMoney::Account::Investment:
rc = "Port";
break;
}
return rc;
}
const QString MyMoneyQifReader::Private::typeToAccountName(const QString& type) const
{
if (type == "reinvint")
return i18nc("Category name", "Reinvested interest");
if (type == "reinvdiv")
return i18nc("Category name", "Reinvested dividend");
if (type == "reinvlg")
return i18nc("Category name", "Reinvested dividend (long term)");
if (type == "reinvsh")
return i18nc("Category name", "Reinvested dividend (short term)");
if (type == "div")
return i18nc("Category name", "Dividend");
if (type == "intinc")
return i18nc("Category name", "Interest");
if (type == "cgshort")
return i18nc("Category name", "Capital Gain (short term)");
if (type == "cgmid")
return i18nc("Category name", "Capital Gain (mid term)");
if (type == "cglong")
return i18nc("Category name", "Capital Gain (long term)");
if (type == "rtrncap")
return i18nc("Category name", "Returned capital");
if (type == "miscinc")
return i18nc("Category name", "Miscellaneous income");
if (type == "miscexp")
return i18nc("Category name", "Miscellaneous expense");
if (type == "sell" || type == "buy")
return i18nc("Category name", "Investment fees");
return i18n("Unknown QIF type %1", type);
}
bool MyMoneyQifReader::Private::isTransfer(QString& tmp, const QString& leftDelim, const QString& rightDelim)
{
// it's a transfer, extract the account name
// I've seen entries like this
//
// S[Mehrwertsteuer]/_VATCode_N_I (The '/' is the Quicken class symbol)
//
// so extracting is a bit more complex and we use a regexp for it
QRegExp exp(QString("\\%1(.*)\\%2(.*)").arg(leftDelim, rightDelim));
bool rc;
if ((rc = (exp.indexIn(tmp) != -1)) == true) {
tmp = exp.cap(1) + exp.cap(2);
tmp = tmp.trimmed();
}
return rc;
}
MyMoneySplit::reconcileFlagE MyMoneyQifReader::Private::reconcileState(const QString& state) const
{
if (state == "X" || state == "R") // Reconciled
return MyMoneySplit::Reconciled;
if (state == "*") // Cleared
return MyMoneySplit::Cleared;
return MyMoneySplit::NotReconciled;
}
MyMoneyQifReader::MyMoneyQifReader() :
d(new Private)
{
m_skipAccount = false;
m_transactionsProcessed =
m_transactionsSkipped = 0;
m_progressCallback = 0;
m_file = 0;
m_entryType = EntryUnknown;
m_processingData = false;
m_userAbort = false;
m_warnedInvestment = false;
m_warnedSecurity = false;
m_warnedPrice = false;
connect(&m_filter, SIGNAL(bytesWritten(qint64)), this, SLOT(slotSendDataToFilter()));
connect(&m_filter, SIGNAL(readyReadStandardOutput()), this, SLOT(slotReceivedDataFromFilter()));
connect(&m_filter, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotImportFinished()));
connect(&m_filter, SIGNAL(readyReadStandardError()), this, SLOT(slotReceivedErrorFromFilter()));
}
MyMoneyQifReader::~MyMoneyQifReader()
{
delete m_file;
delete d;
}
void MyMoneyQifReader::setCategoryMapping(bool map)
{
d->mapCategories = map;
}
void MyMoneyQifReader::setURL(const QUrl &url)
{
m_url = url;
}
void MyMoneyQifReader::setProfile(const QString& profile)
{
m_qifProfile.loadProfile("Profile-" + profile);
}
void MyMoneyQifReader::slotSendDataToFilter()
{
long len;
if (m_file->atEnd()) {
m_filter.closeWriteChannel();
} else {
len = m_file->read(m_buffer, sizeof(m_buffer));
if (len == -1) {
qWarning("Failed to read block from QIF import file");
m_filter.closeWriteChannel();
m_filter.kill();
} else {
m_filter.write(m_buffer, len);
}
}
}
void MyMoneyQifReader::slotReceivedErrorFromFilter()
{
qWarning("%s", qPrintable(QString(m_filter.readAllStandardError())));
}
void MyMoneyQifReader::slotReceivedDataFromFilter()
{
parseReceivedData(m_filter.readAllStandardOutput());
}
void MyMoneyQifReader::parseReceivedData(const QByteArray& data)
{
const char* buff = data.data();
int len = data.length();
m_pos += len;
// signalProgress(m_pos, 0);
while (len) {
// process char
if (*buff == '\n' || *buff == '\r') {
// found EOL
if (!m_lineBuffer.isEmpty()) {
m_qifLines << QString::fromUtf8(m_lineBuffer.trimmed());
}
m_lineBuffer = QByteArray();
} else {
// collect all others
m_lineBuffer += (*buff);
}
++buff;
--len;
}
}
void MyMoneyQifReader::slotImportFinished()
{
// check if the last EOL char was missing and add the trailing line
if (!m_lineBuffer.isEmpty()) {
m_qifLines << QString::fromUtf8(m_lineBuffer.trimmed());
}
qDebug("Read %ld bytes", m_pos);
QTimer::singleShot(0, this, SLOT(slotProcessData()));
}
void MyMoneyQifReader::slotProcessData()
{
signalProgress(-1, -1);
// scan the file and try to determine numeric and date formats
m_qifProfile.autoDetect(m_qifLines);
// the detection is accurate for numeric values, but it could be
// that the dates were too ambiguous so that we have to let the user
// decide which one to pick.
QStringList dateFormats;
m_qifProfile.possibleDateFormats(dateFormats);
QString format;
if (dateFormats.count() > 1) {
bool ok;
format = QInputDialog::getItem(0, i18n("Date format selection"), i18n("Pick the date format that suits your input file"), dateFormats, 05, false, &ok);
if (!ok) {
m_userAbort = true;
}
} else
format = dateFormats.first();
if (!format.isEmpty()) {
m_qifProfile.setInputDateFormat(format);
qDebug("Selected date format: '%s'", qPrintable(format));
} else {
// cancel the process because there is probably nothing to work with
m_userAbort = true;
}
signalProgress(0, m_qifLines.count(), i18n("Importing QIF..."));
QStringList::iterator it;
for (it = m_qifLines.begin(); m_userAbort == false && it != m_qifLines.end(); ++it) {
++m_linenumber;
// qDebug("Proc: '%s'", (*it).data());
if ((*it).startsWith('!')) {
processQifSpecial(*it);
m_qifEntry.clear();
} else if (*it == "^") {
if (m_qifEntry.count() > 0) {
signalProgress(m_linenumber, 0);
processQifEntry();
m_qifEntry.clear();
}
} else {
m_qifEntry += *it;
}
}
d->finishStatement();
qDebug("%d lines processed", m_linenumber);
signalProgress(-1, -1);
emit statementsReady(d->statements);
}
bool MyMoneyQifReader::startImport()
{
bool rc = false;
d->st = MyMoneyStatement();
d->st.m_skipCategoryMatching = !d->mapCategories;
m_dontAskAgain.clear();
m_accountTranslation.clear();
m_userAbort = false;
m_pos = 0;
m_linenumber = 0;
m_filename.clear();
m_data.clear();
if (m_url.isEmpty()) {
return rc;
} else if (m_url.isLocalFile()) {
m_filename = m_url.toLocalFile();
} else {
m_filename = QDir::tempPath();
if(!m_filename.endsWith(QDir::separator()))
m_filename += QDir::separator();
m_filename += m_url.fileName();
qDebug() << "Source:" << m_url.toDisplayString() << "Destination:" << m_filename;
KIO::FileCopyJob *job = KIO::file_copy(m_url, QUrl::fromUserInput(m_filename), -1, KIO::Overwrite);
// KJobWidgets::setWindow(job, kmymoney);
job->exec();
if (job->error()) {
KMessageBox::detailedError(0, i18n("Error while loading file '%1'.", m_url.toDisplayString()),
job->errorString(),
i18n("File access error"));
return rc;
}
}
m_file = new QFile(m_filename);
if (m_file->open(QIODevice::ReadOnly)) {
#ifdef DEBUG_IMPORT
qint64 len;
while (!m_file->atEnd()) {
len = m_file->read(m_buffer, sizeof(m_buffer));
if (len == -1) {
qWarning("Failed to read block from QIF import file");
} else {
parseReceivedData(QByteArray(m_buffer, len));
}
}
QTimer::singleShot(0, this, SLOT(slotImportFinished()));
rc = true;
#else
QString program;
QStringList arguments;
program.clear();
arguments.clear();
// start filter process, use 'cat -' as the default filter
if (m_qifProfile.filterScriptImport().isEmpty()) {
#ifdef Q_OS_WIN32 //krazy:exclude=cpp
// this is the Windows equivalent of 'cat -' but since 'type' does not work with stdin
// we pass the filename converted to native separators as a parameter
program = "cmd.exe";
arguments << "/c";
arguments << "type";
arguments << QDir::toNativeSeparators(m_filename);
#else
program = "cat";
arguments << "-";
#endif
} else {
arguments << m_qifProfile.filterScriptImport().split(' ', QString::KeepEmptyParts);
}
m_entryType = EntryUnknown;
m_filter.setProcessChannelMode(QProcess::MergedChannels);
m_filter.start(program, arguments);
if (m_filter.waitForStarted()) {
signalProgress(0, m_file->size(), i18n("Reading QIF..."));
slotSendDataToFilter();
rc = true;
// emit statementsReady(d->statements);
} else {
KMessageBox::detailedError(0, i18n("Error while running the filter '%1'.", m_filter.program()),
m_filter.errorString(),
i18n("Filter error"));
}
#endif
}
return rc;
}
void MyMoneyQifReader::processQifSpecial(const QString& _line)
{
QString line = _line.mid(1); // get rid of exclamation mark
if (line.left(5).toLower() == QString("type:")) {
line = line.mid(5);
// exportable accounts
if (line.toLower() == "ccard" || KMyMoneyGlobalSettings::qifCreditCard().toLower().contains(line.toLower())) {
- d->accountType = MyMoneyAccount::CreditCard;
+ d->accountType = eMyMoney::Account::CreditCard;
d->firstTransaction = true;
d->transactionType = m_entryType = EntryTransaction;
} else if (line.toLower() == "bank" || KMyMoneyGlobalSettings::qifBank().toLower().contains(line.toLower())) {
- d->accountType = MyMoneyAccount::Checkings;
+ d->accountType = eMyMoney::Account::Checkings;
d->firstTransaction = true;
d->transactionType = m_entryType = EntryTransaction;
} else if (line.toLower() == "cash" || KMyMoneyGlobalSettings::qifCash().toLower().contains(line.toLower())) {
- d->accountType = MyMoneyAccount::Cash;
+ d->accountType = eMyMoney::Account::Cash;
d->firstTransaction = true;
d->transactionType = m_entryType = EntryTransaction;
} else if (line.toLower() == "oth a" || KMyMoneyGlobalSettings::qifAsset().toLower().contains(line.toLower())) {
- d->accountType = MyMoneyAccount::Asset;
+ d->accountType = eMyMoney::Account::Asset;
d->firstTransaction = true;
d->transactionType = m_entryType = EntryTransaction;
} else if (line.toLower() == "oth l" || line.toLower() == i18nc("QIF tag for liability account", "Oth L").toLower()) {
- d->accountType = MyMoneyAccount::Liability;
+ d->accountType = eMyMoney::Account::Liability;
d->firstTransaction = true;
d->transactionType = m_entryType = EntryTransaction;
} else if (line.toLower() == "invst" || line.toLower() == i18nc("QIF tag for investment account", "Invst").toLower()) {
- d->accountType = MyMoneyAccount::Investment;
+ d->accountType = eMyMoney::Account::Investment;
d->transactionType = m_entryType = EntryInvestmentTransaction;
} else if (line.toLower() == "invoice" || KMyMoneyGlobalSettings::qifInvoice().toLower().contains(line.toLower())) {
m_entryType = EntrySkip;
} else if (line.toLower() == "tax") {
m_entryType = EntrySkip;
} else if (line.toLower() == "bill") {
m_entryType = EntrySkip;
// exportable lists
} else if (line.toLower() == "cat" || line.toLower() == i18nc("QIF tag for category", "Cat").toLower()) {
m_entryType = EntryCategory;
} else if (line.toLower() == "security" || line.toLower() == i18nc("QIF tag for security", "Security").toLower()) {
m_entryType = EntrySecurity;
} else if (line.toLower() == "prices" || line.toLower() == i18nc("QIF tag for prices", "Prices").toLower()) {
m_entryType = EntryPrice;
} else if (line.toLower() == "payee") {
m_entryType = EntryPayee;
} else if (line.toLower() == "memorized") {
m_entryType = EntryMemorizedTransaction;
} else if (line.toLower() == "class" || line.toLower() == i18nc("QIF tag for a class", "Class").toLower()) {
m_entryType = EntryClass;
} else if (line.toLower() == "budget") {
m_entryType = EntrySkip;
} else if (line.toLower() == "invitem") {
m_entryType = EntrySkip;
} else if (line.toLower() == "template") {
m_entryType = EntrySkip;
} else {
qWarning("Unknown type code '%s' in QIF file on line %d", qPrintable(line), m_linenumber);
m_entryType = EntrySkip;
}
// option headers
} else if (line.toLower() == "account") {
m_entryType = EntryAccount;
} else if (line.toLower() == "option:autoswitch") {
m_entryType = EntryAccount;
} else if (line.toLower() == "clear:autoswitch") {
m_entryType = d->transactionType;
}
}
void MyMoneyQifReader::processQifEntry()
{
// This method processes a 'QIF Entry' which is everything between two caret
// signs
//
try {
switch (m_entryType) {
case EntryCategory:
processCategoryEntry();
break;
case EntryUnknown:
qDebug() << "Line " << m_linenumber << ": Warning: Found an entry without a type being specified. Checking assumed.";
processTransactionEntry();
break;
case EntryTransaction:
processTransactionEntry();
break;
case EntryInvestmentTransaction:
processInvestmentTransactionEntry();
break;
case EntryAccount:
processAccountEntry();
break;
case EntrySecurity:
processSecurityEntry();
break;
case EntryPrice:
processPriceEntry();
break;
case EntryPayee:
processPayeeEntry();
break;
case EntryClass:
qDebug() << "Line " << m_linenumber << ": Classes are not yet supported!";
break;
case EntryMemorizedTransaction:
qDebug() << "Line " << m_linenumber << ": Memorized transactions are not yet implemented!";
break;
case EntrySkip:
break;
default:
qDebug() << "Line " << m_linenumber << ": EntryType " << m_entryType << " not yet implemented!";
break;
}
} catch (const MyMoneyException &e) {
if (e.what() != "USERABORT") {
qDebug() << "Line " << m_linenumber << ": Unhandled error: " << e.what();
} else {
m_userAbort = true;
}
}
}
const QString MyMoneyQifReader::extractLine(const QChar& id, int cnt)
{
QStringList::ConstIterator it;
m_extractedLine = -1;
for (it = m_qifEntry.constBegin(); it != m_qifEntry.constEnd(); ++it) {
++m_extractedLine;
if ((*it)[0] == id) {
if (cnt-- == 1) {
return (*it).mid(1);
}
}
}
m_extractedLine = -1;
return QString();
}
bool MyMoneyQifReader::extractSplits(QList<qSplit>& listqSplits) const
{
// *** With apologies to QString MyMoneyQifReader::extractLine ***
QStringList::ConstIterator it;
bool ret = false;
bool memoPresent = false;
int neededCount = 0;
qSplit q;
for (it = m_qifEntry.constBegin(); it != m_qifEntry.constEnd(); ++it) {
if (((*it)[0] == 'S') || ((*it)[0] == '$') || ((*it)[0] == 'E')) {
memoPresent = false; // in case no memo line in this split
if ((*it)[0] == 'E') {
q.m_strMemo = (*it).mid(1); // 'E' = Memo
d->fixMultiLineMemo(q.m_strMemo);
memoPresent = true; // This transaction contains memo
} else if ((*it)[0] == 'S') {
q.m_strCategoryName = (*it).mid(1); // 'S' = CategoryName
neededCount ++;
} else if ((*it)[0] == '$') {
q.m_amount = (*it).mid(1); // '$' = Amount
neededCount ++;
}
if (neededCount > 1) { // CategoryName & Amount essential
listqSplits += q; // Add valid split
if (!memoPresent) { // If no memo, clear previous
q.m_strMemo.clear();
}
qSplit q; // Start new split
neededCount = 0;
ret = true;
}
}
}
return ret;
}
#if 0
-void MyMoneyQifReader::processMSAccountEntry(const MyMoneyAccount::accountTypeE accountType)
+void MyMoneyQifReader::processMSAccountEntry(const eMyMoney::Account accountType)
{
if (extractLine('P').toLower() == m_qifProfile.openingBalanceText().toLower()) {
m_account = MyMoneyAccount();
m_account.setAccountType(accountType);
QString txt = extractLine('T');
MyMoneyMoney balance = m_qifProfile.value('T', txt);
QDate date = m_qifProfile.date(extractLine('D'));
m_account.setOpeningDate(date);
QString name = extractLine('L');
if (name.left(1) == m_qifProfile.accountDelimiter().left(1)) {
name = name.mid(1, name.length() - 2);
}
d->st_AccountName = name;
m_account.setName(name);
selectOrCreateAccount(Select, m_account, balance);
d->st.m_accountId = m_account.id();
if (! balance.isZero()) {
MyMoneyFile* file = MyMoneyFile::instance();
QString openingtxid = file->openingBalanceTransaction(m_account);
MyMoneyFileTransaction ft;
if (! openingtxid.isEmpty()) {
MyMoneyTransaction openingtx = file->transaction(openingtxid);
MyMoneySplit split = openingtx.splitByAccount(m_account.id());
if (split.shares() != balance) {
const MyMoneySecurity& sec = file->security(m_account.currencyId());
if (KMessageBox::questionYesNo(
KMyMoneyUtils::mainWindow(),
i18n("The %1 account currently has an opening balance of %2. This QIF file reports an opening balance of %3. Would you like to overwrite the current balance with the one from the QIF file?", m_account.name(), split.shares().formatMoney(m_account, sec), balance.formatMoney(m_account, sec)),
i18n("Overwrite opening balance"),
KStandardGuiItem::yes(),
KStandardGuiItem::no(),
"OverwriteOpeningBalance")
== KMessageBox::Yes) {
file->removeTransaction(openingtx);
m_account.setOpeningDate(date);
file->createOpeningBalanceTransaction(m_account, balance);
}
}
} else {
// Add an opening balance
m_account.setOpeningDate(date);
file->createOpeningBalanceTransaction(m_account, balance);
}
ft.commit();
}
} else {
// for some unknown reason, Quicken 2001 generates the following (somewhat
// misleading) sequence of lines:
//
// 1: !Account
// 2: NAT&T Universal
// 3: DAT&T Univers(...xxxx) [CLOSED]
// 4: TCCard
// 5: ^
// 6: !Type:CCard
// 7: !Account
// 8: NCFCU Visa
// 9: DRick's CFCU Visa card (...xxxx)
// 10: TCCard
// 11: ^
// 12: !Type:CCard
// 13: D1/ 4' 1
//
// Lines 1-5 are processed via processQifEntry() and processAccountEntry()
// Then Quicken issues line 6 but since the account does not carry any
// transaction does not write an end delimiter. Arrrgh! So we end up with
// a QIF entry comprising of lines 6-11 and end up in this routine. Actually,
// lines 7-11 are the leadin for the next account. So we check here if
// the !Type:xxx record also contains an !Account line and process the
// entry as required.
//
// (Ace) I think a better solution here is to handle exclamation point
// lines separately from entries. In the above case:
// Line 1 would set the mode to "account entries".
// Lines 2-5 would be interpreted as an account entry. This would set m_account.
// Line 6 would set the mode to "cc transaction entries".
// Line 7 would immediately set the mode to "account entries" again
// Lines 8-11 would be interpreted as an account entry. This would set m_account.
// Line 12 would set the mode to "cc transaction entries"
// Lines 13+ would be interpreted as cc transaction entries, and life is good
int exclamationCnt = 1;
QString category;
do {
category = extractLine('!', exclamationCnt++);
} while (!category.isEmpty() && category != "Account");
// we have such a weird empty account
if (category == "Account") {
processAccountEntry();
} else {
selectOrCreateAccount(Select, m_account);
d->st_AccountName = m_account.name();
d->st.m_strAccountName = m_account.name();
d->st.m_accountId = m_account.id();
d->st.m_strAccountNumber = m_account.id();
m_account.setNumber(m_account.id());
if (m_entryType == EntryInvestmentTransaction)
processInvestmentTransactionEntry();
else
processTransactionEntry();
}
}
}
#endif
void MyMoneyQifReader::processPayeeEntry()
{
// TODO
}
void MyMoneyQifReader::processCategoryEntry()
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount account = MyMoneyAccount();
account.setName(extractLine('N'));
account.setDescription(extractLine('D'));
MyMoneyAccount parentAccount;
//The extractline routine will more than likely return 'empty',
// so also have to test that either the 'I' or 'E' was detected
//and set up accounts accordingly.
if ((!extractLine('I').isEmpty()) || (m_extractedLine != -1)) {
- account.setAccountType(MyMoneyAccount::Income);
+ account.setAccountType(eMyMoney::Account::Income);
parentAccount = file->income();
} else if ((!extractLine('E').isEmpty()) || (m_extractedLine != -1)) {
- account.setAccountType(MyMoneyAccount::Expense);
+ account.setAccountType(eMyMoney::Account::Expense);
parentAccount = file->expense();
}
// check if we can find the account already in the file
auto acc = findAccount(account, MyMoneyAccount());
// if not, we just create it
if (acc.id().isEmpty()) {
MyMoneyAccount brokerage;
file->createAccount(account, parentAccount, brokerage, MyMoneyMoney());
}
}
const MyMoneyAccount& MyMoneyQifReader::findAccount(const MyMoneyAccount& acc, const MyMoneyAccount& parent) const
{
static MyMoneyAccount nullAccount;
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyAccount> parents;
try {
// search by id
if (!acc.id().isEmpty()) {
return file->account(acc.id());
}
// collect the parents. in case parent does not have an id, we scan the all top-level accounts
if (parent.id().isEmpty()) {
parents << file->asset();
parents << file->liability();
parents << file->income();
parents << file->expense();
parents << file->equity();
} else {
parents << parent;
}
QList<MyMoneyAccount>::const_iterator it_p;
for (it_p = parents.constBegin(); it_p != parents.constEnd(); ++it_p) {
MyMoneyAccount parentAccount = *it_p;
// search by name (allow hierarchy)
int pos;
// check for ':' in the name and use it as separator for a hierarchy
QString name = acc.name();
bool notFound = false;
while ((pos = name.indexOf(MyMoneyFile::AccountSeperator)) != -1) {
QString part = name.left(pos);
QString remainder = name.mid(pos + 1);
const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, part);
// if account has not been found, continue with next top level parent
if (existingAccount.id().isEmpty()) {
notFound = true;
break;
}
parentAccount = existingAccount;
name = remainder;
}
if (notFound)
continue;
const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, name);
if (!existingAccount.id().isEmpty()) {
- if (acc.accountType() != MyMoneyAccount::UnknownAccountType) {
+ if (acc.accountType() != eMyMoney::Account::Unknown) {
if (acc.accountType() != existingAccount.accountType())
continue;
}
return existingAccount;
}
}
} catch (const MyMoneyException &e) {
KMessageBox::error(0, i18n("Unable to find account: %1", e.what()));
}
return nullAccount;
}
const QString MyMoneyQifReader::transferAccount(const QString& name, bool useBrokerage)
{
QString accountId;
QStringList tmpEntry = m_qifEntry; // keep temp copies
MyMoneyAccount tmpAccount = m_account;
m_qifEntry.clear(); // and construct a temp entry to create/search the account
m_qifEntry << QString("N%1").arg(name);
m_qifEntry << QString("Tunknown");
m_qifEntry << QString("D%1").arg(i18n("Autogenerated by QIF importer"));
accountId = processAccountEntry(false);
// in case we found a reference to an investment account, we need
// to switch to the brokerage account instead.
MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId);
- if (useBrokerage && (acc.accountType() == MyMoneyAccount::Investment)) {
+ if (useBrokerage && (acc.accountType() == eMyMoney::Account::Investment)) {
m_qifEntry.clear(); // and construct a temp entry to create/search the account
m_qifEntry << QString("N%1").arg(acc.brokerageName());
m_qifEntry << QString("Tunknown");
m_qifEntry << QString("D%1").arg(i18n("Autogenerated by QIF importer"));
accountId = processAccountEntry(false);
}
m_qifEntry = tmpEntry; // restore local copies
m_account = tmpAccount;
return accountId;
}
-void MyMoneyQifReader::createOpeningBalance(MyMoneyAccount::_accountTypeE accType)
+void MyMoneyQifReader::createOpeningBalance(eMyMoney::Account accType)
{
MyMoneyFile* file = MyMoneyFile::instance();
// if we don't have a name for the current account we need to extract the name from the L-record
if (m_account.name().isEmpty()) {
QString name = extractLine('L');
if (name.isEmpty()) {
name = i18n("QIF imported, no account name supplied");
}
d->isTransfer(name, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1));
QStringList entry = m_qifEntry; // keep a temp copy
m_qifEntry.clear(); // and construct a temp entry to create/search the account
m_qifEntry << QString("N%1").arg(name);
m_qifEntry << QString("T%1").arg(d->accountTypeToQif(accType));
m_qifEntry << QString("D%1").arg(i18n("Autogenerated by QIF importer"));
processAccountEntry();
m_qifEntry = entry; // restore local copy
}
MyMoneyFileTransaction ft;
try {
bool needCreate = true;
MyMoneyAccount acc = m_account;
// in case we're dealing with an investment account, we better use
// the accompanying brokerage account for the opening balance
acc = file->accountByName(m_account.brokerageName());
// check if we already have an opening balance transaction
QString tid = file->openingBalanceTransaction(acc);
MyMoneyTransaction ot;
if (!tid.isEmpty()) {
ot = file->transaction(tid);
MyMoneySplit s0 = ot.splitByAccount(acc.id());
// if the value is the same, we can silently skip this transaction
if (s0.shares() == m_qifProfile.value('T', extractLine('T'))) {
needCreate = false;
}
if (needCreate) {
// in case we create it anyway, we issue a warning to the user to check it manually
KMessageBox::sorry(0, QString("<qt>%1</qt>").arg(i18n("KMyMoney has imported a second opening balance transaction into account <b>%1</b> which differs from the one found already on file. Please correct this manually once the import is done.", acc.name())), i18n("Opening balance problem"));
}
}
if (needCreate) {
acc.setOpeningDate(m_qifProfile.date(extractLine('D')));
file->modifyAccount(acc);
MyMoneyTransaction t = file->createOpeningBalanceTransaction(acc, m_qifProfile.value('T', extractLine('T')));
if (!t.id().isEmpty()) {
t.setImported();
file->modifyTransaction(t);
}
ft.commit();
}
// make sure to use the updated version of the account
if (m_account.id() == acc.id())
m_account = acc;
// remember which account we created
d->st.m_accountId = m_account.id();
} catch (const MyMoneyException &e) {
KMessageBox::detailedError(0,
i18n("Error while creating opening balance transaction"),
QString("%1(%2):%3").arg(e.file()).arg(e.line()).arg(e.what()),
i18n("File access error"));
}
}
void MyMoneyQifReader::processTransactionEntry()
{
++m_transactionsProcessed;
// in case the user selected to skip the account or the account
// was not found we skip this transaction
/*
if(m_account.id().isEmpty()) {
m_transactionsSkipped++;
return;
}
*/
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyStatement::Split s1;
MyMoneyStatement::Transaction tr;
QString tmp;
QString accountId;
int pos;
QString payee = extractLine('P');
unsigned long h;
h = MyMoneyTransaction::hash(m_qifEntry.join(";"));
QString hashBase;
hashBase.sprintf("%s-%07lx", qPrintable(m_qifProfile.date(extractLine('D')).toString(Qt::ISODate)), h);
int idx = 1;
QString hash;
for (;;) {
hash = QString("%1-%2").arg(hashBase).arg(idx);
QMap<QString, bool>::const_iterator it;
it = d->m_hashMap.constFind(hash);
if (it == d->m_hashMap.constEnd()) {
d->m_hashMap[hash] = true;
break;
}
++idx;
}
tr.m_strBankID = hash;
if (d->firstTransaction) {
// check if this is an opening balance transaction and process it out of the statement
if (!payee.isEmpty() && ((payee.toLower() == "opening balance") || KMyMoneyGlobalSettings::qifOpeningBalance().toLower().contains(payee.toLower()))) {
createOpeningBalance(d->accountType);
d->firstTransaction = false;
return;
}
}
// Process general transaction data
if (d->st.m_accountId.isEmpty())
d->st.m_accountId = m_account.id();
s1.m_accountId = d->st.m_accountId;
switch (d->accountType) {
- case MyMoneyAccount::Checkings:
+ case eMyMoney::Account::Checkings:
d->st.m_eType=MyMoneyStatement::etCheckings;
break;
- case MyMoneyAccount::Savings:
+ case eMyMoney::Account::Savings:
d->st.m_eType=MyMoneyStatement::etSavings;
break;
- case MyMoneyAccount::Investment:
+ case eMyMoney::Account::Investment:
d->st.m_eType=MyMoneyStatement::etInvestment;
break;
- case MyMoneyAccount::CreditCard:
+ case eMyMoney::Account::CreditCard:
d->st.m_eType=MyMoneyStatement::etCreditCard;
break;
default:
d->st.m_eType=MyMoneyStatement::etNone;
break;
}
tr.m_datePosted = (m_qifProfile.date(extractLine('D')));
if (!tr.m_datePosted.isValid()) {
int rc = KMessageBox::warningContinueCancel(0,
i18n("The date entry \"%1\" read from the file cannot be interpreted through the current "
"date profile setting of \"%2\".\n\nPressing \"Continue\" will "
"assign todays date to the transaction. Pressing \"Cancel\" will abort "
"the import operation. You can then restart the import and select a different "
"QIF profile or create a new one.", extractLine('D'), m_qifProfile.inputDateFormat()),
i18n("Invalid date format"));
switch (rc) {
case KMessageBox::Continue:
tr.m_datePosted = (QDate::currentDate());
break;
case KMessageBox::Cancel:
throw MYMONEYEXCEPTION("USERABORT");
break;
}
}
tmp = extractLine('L');
pos = tmp.lastIndexOf("--");
if (tmp.left(1) == m_qifProfile.accountDelimiter().left(1)) {
// it's a transfer, so we wipe the memo
// tmp = ""; why??
// st.m_strAccountName = tmp;
} else if (pos != -1) {
// what's this?
// t.setValue("Dialog", tmp.mid(pos+2));
tmp = tmp.left(pos);
}
// t.setMemo(tmp);
// Assign the "#" field to the transaction's bank id
// This is the custom KMM extension to QIF for a unique ID
tmp = extractLine('#');
if (!tmp.isEmpty()) {
tr.m_strBankID = QString("ID %1").arg(tmp);
}
#if 0
// Collect data for the account's split
s1.m_accountId = m_account.id();
tmp = extractLine('S');
pos = tmp.findRev("--");
if (pos != -1) {
tmp = tmp.left(pos);
}
if (tmp.left(1) == m_qifProfile.accountDelimiter().left(1))
// it's a transfer, extract the account name
tmp = tmp.mid(1, tmp.length() - 2);
s1.m_strCategoryName = tmp;
#endif
// TODO (Ace) Deal with currencies more gracefully. QIF cannot deal with multiple
// currencies, so we should assume that transactions imported into a given
// account are in THAT ACCOUNT's currency. If one of those involves a transfer
// to an account with a different currency, value and shares should be
// different. (Shares is in the target account's currency, value is in the
// transaction's)
s1.m_amount = m_qifProfile.value('T', extractLine('T'));
tr.m_amount = m_qifProfile.value('T', extractLine('T'));
tr.m_shares = m_qifProfile.value('T', extractLine('T'));
tmp = extractLine('N');
if (!tmp.isEmpty())
tr.m_strNumber = tmp;
if (!payee.isEmpty()) {
tr.m_strPayee = payee;
}
tr.m_reconcile = d->reconcileState(extractLine('C'));
tr.m_strMemo = extractLine('M');
d->fixMultiLineMemo(tr.m_strMemo);
s1.m_strMemo = tr.m_strMemo;
// tr.m_listSplits.append(s1);
// split transaction
// ****** ensure each field is ******
// * attached to correct split *
QList<qSplit> listqSplits;
if (! extractSplits(listqSplits)) {
MyMoneyAccount account;
// use the same values for the second split, but clear the ID and reverse the value
MyMoneyStatement::Split s2 = s1;
s2.m_reconcile = tr.m_reconcile;
s2.m_amount = (-s1.m_amount);
// s2.clearId();
// standard transaction
tmp = extractLine('L');
if (d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1))) {
accountId = transferAccount(tmp, false);
} else {
/* pos = tmp.findRev("--");
if(pos != -1) {
t.setValue("Dialog", tmp.mid(pos+2));
tmp = tmp.left(pos);
}*/
// it's an expense / income
tmp = tmp.trimmed();
accountId = file->checkCategory(tmp, s1.m_amount, s2.m_amount);
}
if (!accountId.isEmpty()) {
try {
MyMoneyAccount account = file->account(accountId);
// FIXME: check that the type matches and ask if not
- if (account.accountType() == MyMoneyAccount::Investment) {
+ if (account.accountType() == eMyMoney::Account::Investment) {
qDebug() << "Line " << m_linenumber << ": Cannot transfer to/from an investment account. Transaction ignored.";
return;
}
if (account.id() == m_account.id()) {
qDebug() << "Line " << m_linenumber << ": Cannot transfer to the same account. Transfer ignored.";
accountId.clear();
}
} catch (const MyMoneyException &) {
qDebug() << "Line " << m_linenumber << ": Account with id " << accountId.data() << " not found";
accountId.clear();
}
}
if (!accountId.isEmpty()) {
s2.m_accountId = accountId;
s2.m_strCategoryName = tmp;
tr.m_listSplits.append(s2);
}
} else {
int count;
for (count = 1; count <= listqSplits.count(); ++count) { // Use true splits count
MyMoneyStatement::Split s2 = s1;
s2.m_amount = (-m_qifProfile.value('$', listqSplits[count-1].m_amount)); // Amount of split
s2.m_strMemo = listqSplits[count-1].m_strMemo; // Memo in split
tmp = listqSplits[count-1].m_strCategoryName; // Category in split
if (d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1))) {
accountId = transferAccount(tmp, false);
} else {
pos = tmp.lastIndexOf("--");
if (pos != -1) {
tmp = tmp.left(pos);
}
tmp = tmp.trimmed();
accountId = file->checkCategory(tmp, s1.m_amount, s2.m_amount);
}
if (!accountId.isEmpty()) {
try {
MyMoneyAccount account = file->account(accountId);
// FIXME: check that the type matches and ask if not
- if (account.accountType() == MyMoneyAccount::Investment) {
+ if (account.accountType() == eMyMoney::Account::Investment) {
qDebug() << "Line " << m_linenumber << ": Cannot convert a split transfer to/from an investment account. Split removed. Total amount adjusted from " << tr.m_amount.formatMoney("", 2) << " to " << (tr.m_amount + s2.m_amount).formatMoney("", 2) << "\n";
tr.m_amount += s2.m_amount;
continue;
}
if (account.id() == m_account.id()) {
qDebug() << "Line " << m_linenumber << ": Cannot transfer to the same account. Transfer ignored.";
accountId.clear();
}
} catch (const MyMoneyException &) {
qDebug() << "Line " << m_linenumber << ": Account with id " << accountId.data() << " not found";
accountId.clear();
}
}
if (!accountId.isEmpty()) {
s2.m_accountId = accountId;
s2.m_strCategoryName = tmp;
tr.m_listSplits += s2;
// in case the transaction does not have a memo and we
// process the first split just copy the memo over
if (tr.m_listSplits.count() == 1 && tr.m_strMemo.isEmpty())
tr.m_strMemo = s2.m_strMemo;
} else {
// TODO add an option to create a "Unassigned" category
// for now, we just drop the split which will show up as unbalanced
// transaction in the KMyMoney ledger view
}
}
}
// Add the transaction to the statement
d->st.m_listTransactions += tr;
}
void MyMoneyQifReader::processInvestmentTransactionEntry()
{
// qDebug() << "Investment Transaction:" << m_qifEntry.count() << " lines";
/*
Items for Investment Accounts
Field Indicator Explanation
D Date
N Action
Y Security (NAME, not symbol)
I Price
Q Quantity (number of shares or split ratio)
T Transaction amount
C Cleared status
P Text in the first line for transfers and reminders (Payee)
M Memo
O Commission
L Account for the transfer
$ Amount transferred
^ End of the entry
It will be presumed all transactions are to the associated cash account, if
one exists, unless otherwise noted by the 'L' field.
Expense/Income categories will be automatically generated, "_Dividend",
"_InterestIncome", etc.
*/
MyMoneyStatement::Transaction tr;
d->st.m_eType = MyMoneyStatement::etInvestment;
// t.setCommodity(m_account.currencyId());
// 'D' field: Date
QDate date = m_qifProfile.date(extractLine('D'));
if (date.isValid())
tr.m_datePosted = date;
else {
int rc = KMessageBox::warningContinueCancel(0,
i18n("The date entry \"%1\" read from the file cannot be interpreted through the current "
"date profile setting of \"%2\".\n\nPressing \"Continue\" will "
"assign todays date to the transaction. Pressing \"Cancel\" will abort "
"the import operation. You can then restart the import and select a different "
"QIF profile or create a new one.", extractLine('D'), m_qifProfile.inputDateFormat()),
i18n("Invalid date format"));
switch (rc) {
case KMessageBox::Continue:
tr.m_datePosted = QDate::currentDate();
break;
case KMessageBox::Cancel:
throw MYMONEYEXCEPTION("USERABORT");
break;
}
}
// 'M' field: Memo
QString memo = extractLine('M');
d->fixMultiLineMemo(memo);
tr.m_strMemo = memo;
unsigned long h;
h = MyMoneyTransaction::hash(m_qifEntry.join(";"));
QString hashBase;
hashBase.sprintf("%s-%07lx", qPrintable(m_qifProfile.date(extractLine('D')).toString(Qt::ISODate)), h);
int idx = 1;
QString hash;
for (;;) {
hash = QString("%1-%2").arg(hashBase).arg(idx);
QMap<QString, bool>::const_iterator it;
it = d->m_hashMap.constFind(hash);
if (it == d->m_hashMap.constEnd()) {
d->m_hashMap[hash] = true;
break;
}
++idx;
}
tr.m_strBankID = hash;
// '#' field: BankID
QString tmp = extractLine('#');
if (! tmp.isEmpty())
tr.m_strBankID = QString("ID %1").arg(tmp);
// Reconciliation flag
tr.m_reconcile = d->reconcileState(extractLine('C'));
// 'O' field: Fees
tr.m_fees = m_qifProfile.value('T', extractLine('O'));
// 'T' field: Amount
MyMoneyMoney amount = m_qifProfile.value('T', extractLine('T'));
tr.m_amount = amount;
MyMoneyStatement::Price price;
price.m_date = date;
price.m_strSecurity = extractLine('Y');
price.m_amount = m_qifProfile.value('T', extractLine('I'));
#if 0 // we must check for that later, because certain activities don't need a security
// 'Y' field: Security name
QString securityname = extractLine('Y').toLower();
if (securityname.isEmpty()) {
qDebug() << "Line " << m_linenumber << ": Investment transaction without a security is not supported.";
return;
}
tr.m_strSecurity = securityname;
#endif
#if 0
// For now, we let the statement reader take care of that.
// The big problem here is that the Y field is not the SYMBOL, it's the NAME.
// The name is not very unique, because people could have used slightly different
// abbreviations or ordered words differently, etc.
//
// If there is a perfect name match with a subordinate stock account, great.
// More likely, we have to rely on the QIF file containing !Type:Security
// records, which tell us the mapping from name to symbol.
//
// Therefore, generally it is not recommended to import a QIF file containing
// investment transactions but NOT containing security records.
QString securitysymbol = m_investmentMap[securityname];
// the correct account is the stock account which matches two criteria:
// (1) it is a sub-account of the selected investment account, and either
// (2a) the security name of the transaction matches the name of the security, OR
// (2b) the security name of the transaction maps to a symbol which matches the symbol of the security
// search through each subordinate account
bool found = false;
MyMoneyAccount thisaccount = m_account;
QStringList accounts = thisaccount.accountList();
QStringList::const_iterator it_account = accounts.begin();
while (!found && it_account != accounts.end()) {
QString currencyid = file->account(*it_account).currencyId();
MyMoneySecurity security = file->security(currencyid);
QString symbol = security.tradingSymbol().toLower();
QString name = security.name().toLower();
if (securityname == name || securitysymbol == symbol) {
d->st_AccountId = *it_account;
s1.m_accountId = *it_account;
thisaccount = file->account(*it_account);
found = true;
#if 0
// update the price, while we're here. in the future, this should be
// an option
QString basecurrencyid = file->baseCurrency().id();
MyMoneyPrice price = file->price(currencyid, basecurrencyid, t_in.m_datePosted, true);
if (!price.isValid()) {
MyMoneyPrice newprice(currencyid, basecurrencyid, t_in.m_datePosted, t_in.m_moneyAmount / t_in.m_dShares, i18n("Statement Importer"));
file->addPrice(newprice);
}
#endif
}
++it_account;
}
if (!found) {
qDebug() << "Line " << m_linenumber << ": Security " << securityname << " not found in this account. Transaction ignored.";
// If the security is not known, notify the user
// TODO (Ace) A "SelectOrCreateAccount" interface for investments
KMessageBox::information(0, i18n("This investment account does not contain the \"%1\" security. "
"Transactions involving this security will be ignored.", securityname),
i18n("Security not found"),
QString("MissingSecurity%1").arg(securityname.trimmed()));
return;
}
#endif
// 'Y' field: Security
tr.m_strSecurity = extractLine('Y');
// 'Q' field: Quantity
MyMoneyMoney quantity = m_qifProfile.value('T', extractLine('Q'));
// 'N' field: Action
QString action = extractLine('N').toLower();
// remove trailing X, which seems to have no purpose (?!)
bool xAction = false;
if (action.endsWith('x')) {
action = action.left(action.length() - 1);
xAction = true;
}
tmp = extractLine('L');
// if the action ends in an X, the L-Record contains the asset account
// to which the dividend should be transferred. In the other cases, it
// may contain a category that identifies the income category for the
// dividend payment
if ((xAction == true)
|| (d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1)) == true)) {
tmp = tmp.remove(QRegExp("[\\[\\]]")); // xAction != true so ignore any'[ and ]'
if (!tmp.isEmpty()) { // use 'L' record name
tr.m_strBrokerageAccount = tmp;
transferAccount(tmp); // make sure the account exists
} else {
tr.m_strBrokerageAccount = m_account.brokerageName();// use brokerage account
transferAccount(m_account.brokerageName()); // make sure the account exists
}
} else {
tmp = tmp.remove(QRegExp("[\\[\\]]")); // xAction != true so ignore any'[ and ]'
tr.m_strInterestCategory = tmp;
tr.m_strBrokerageAccount = m_account.brokerageName();
}
// Whether to create a cash split for the other side of the value
QString accountname; //= extractLine('L');
if (action == "reinvint" || action == "reinvdiv" || action == "reinvlg" || action == "reinvsh") {
d->st.m_listPrices += price;
tr.m_shares = quantity;
tr.m_eAction = (MyMoneyStatement::Transaction::eaReinvestDividend);
tr.m_price = m_qifProfile.value('I', extractLine('I'));
tr.m_strInterestCategory = extractLine('L');
if (tr.m_strInterestCategory.isEmpty()) {
tr.m_strInterestCategory = d->typeToAccountName(action);
}
} else if (action == "div" || action == "cgshort" || action == "cgmid" || action == "cglong" || action == "rtrncap") {
tr.m_eAction = (MyMoneyStatement::Transaction::eaCashDividend);
// make sure, we have valid category. Either taken from the L-Record above,
// or derived from the action code
if (tr.m_strInterestCategory.isEmpty()) {
tr.m_strInterestCategory = d->typeToAccountName(action);
}
// For historic reasons (coming from the OFX importer) the statement
// reader expects the dividend with a reverse sign. So we just do that.
tr.m_amount -= tr.m_fees;
// We need an extra split which will be the zero-amount investment split
// that serves to mark this transaction as a cash dividend and note which
// stock account it belongs to.
MyMoneyStatement::Split s2;
s2.m_amount = MyMoneyMoney();
s2.m_strCategoryName = extractLine('Y');
tr.m_listSplits.append(s2);
} else if (action == "intinc" || action == "miscinc" || action == "miscexp") {
tr.m_eAction = (MyMoneyStatement::Transaction::eaInterest);
if (action == "miscexp")
tr.m_eAction = (MyMoneyStatement::Transaction::eaFees);
// make sure, we have a valid category. Either taken from the L-Record above,
// or derived from the action code
if (tr.m_strInterestCategory.isEmpty()) {
tr.m_strInterestCategory = d->typeToAccountName(action);
}
if (action == "intinc") {
MyMoneyMoney price = m_qifProfile.value('I', extractLine('I'));
tr.m_amount -= tr.m_fees;
if ((!quantity.isZero()) && (!price.isZero()))
tr.m_amount = -(quantity * price);
} else
// For historic reasons (coming from the OFX importer) the statement
// reader expects the dividend with a reverse sign. So we just do that.
if (action != "miscexp")
tr.m_amount = -(amount - tr.m_fees);
if (tr.m_strMemo.isEmpty())
tr.m_strMemo = (QString("%1 %2").arg(extractLine('Y')).arg(d->typeToAccountName(action))).trimmed();
} else if (action == "xin" || action == "xout") {
QString payee = extractLine('P');
if (!payee.isEmpty() && ((payee.toLower() == "opening balance") || KMyMoneyGlobalSettings::qifOpeningBalance().toLower().contains(payee.toLower()))) {
- createOpeningBalance(MyMoneyAccount::Investment);
+ createOpeningBalance(eMyMoney::Account::Investment);
return;
}
tr.m_eAction = (MyMoneyStatement::Transaction::eaNone);
MyMoneyStatement::Split s2;
QString tmp = extractLine('L');
if (d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1))) {
s2.m_accountId = transferAccount(tmp);
s2.m_strCategoryName = tmp;
} else {
s2.m_strCategoryName = extractLine('L');
if (tr.m_strInterestCategory.isEmpty()) {
s2.m_strCategoryName = d->typeToAccountName(action);
}
}
if (action == "xout")
tr.m_amount = -tr.m_amount;
s2.m_amount = -tr.m_amount;
tr.m_listSplits.append(s2);
} else if (action == "buy") {
d->st.m_listPrices += price;
tr.m_price = m_qifProfile.value('I', extractLine('I'));
tr.m_shares = quantity;
tr.m_amount = -amount;
tr.m_eAction = (MyMoneyStatement::Transaction::eaBuy);
} else if (action == "sell") {
d->st.m_listPrices += price;
tr.m_price = m_qifProfile.value('I', extractLine('I'));
tr.m_shares = -quantity;
tr.m_amount = amount;
tr.m_eAction = (MyMoneyStatement::Transaction::eaSell);
} else if (action == "shrsin") {
tr.m_shares = quantity;
tr.m_eAction = (MyMoneyStatement::Transaction::eaShrsin);
} else if (action == "shrsout") {
tr.m_shares = -quantity;
tr.m_eAction = (MyMoneyStatement::Transaction::eaShrsout);
} else if (action == "stksplit") {
MyMoneyMoney splitfactor = (quantity / MyMoneyMoney(10, 1)).reduce();
// Stock splits not supported
// qDebug() << "Line " << m_linenumber << ": Stock split not supported (date=" << date << " security=" << securityname << " factor=" << splitfactor.toString() << ")";
// s1.setShares(splitfactor);
// s1.setValue(0);
// s1.setAction(MyMoneySplit::ActionSplitShares);
// return;
} else {
// Unsupported action type
qDebug() << "Line " << m_linenumber << ": Unsupported transaction action (" << action << ")";
return;
}
d->st.m_strAccountName = accountname; // accountname appears not to get set
d->st.m_listTransactions += tr;
/*************************************************************************
*
* These transactions are natively supported by KMyMoney
*
*************************************************************************/
/*
D1/ 3' 5
NShrsIn
YGENERAL MOTORS CORP 52BR1
I20
Q200
U4,000.00
T4,000.00
M200 shares added to account @ $20/share
^
*/
/*
^
D1/14' 5
NShrsOut
YTEMPLETON GROWTH 97GJ0
Q50
90 ^
*/
/*
D1/28' 5
NBuy
YGENERAL MOTORS CORP 52BR1
I24.35
Q100
U2,435.00
T2,435.00
^
*/
/*
D1/ 5' 5
NSell
YUnited Vanguard
I8.41
Q50
U420.50
T420.50
^
*/
/*
D1/ 7' 5
NReinvDiv
YFRANKLIN INCOME 97GM2
I38
Q1
U38.00
T38.00
^
*/
/*************************************************************************
*
* These transactions are all different kinds of income. (Anything that
* follows the DNYUT pattern). They are all handled the same, the only
* difference is which income account the income is placed into. By
* default, it's placed into _xxx where xxx is the right side of the
* N field. e.g. NDiv transaction goes into the _Div account
*
*************************************************************************/
/*
D1/10' 5
NDiv
YTEMPLETON GROWTH 97GJ0
U10.00
T10.00
^
*/
/*
D1/10' 5
NIntInc
YTEMPLETON GROWTH 97GJ0
U20.00
T20.00
^
*/
/*
D1/10' 5
NCGShort
YTEMPLETON GROWTH 97GJ0
U111.00
T111.00
^
*/
/*
D1/10' 5
NCGLong
YTEMPLETON GROWTH 97GJ0
U333.00
T333.00
^
*/
/*
D1/10' 5
NCGMid
YTEMPLETON GROWTH 97GJ0
U222.00
T222.00
^
*/
/*
D2/ 2' 5
NRtrnCap
YFRANKLIN INCOME 97GM2
U1,234.00
T1,234.00
^
*/
/*************************************************************************
*
* These transactions deal with miscellaneous activity that KMyMoney
* does not support, but may support in the future.
*
*************************************************************************/
/* Note the Q field is the split ratio per 10 shares, so Q12.5 is a
12.5:10 split, otherwise known as 5:4.
D1/14' 5
NStkSplit
YIBM
Q12.5
^
*/
/*************************************************************************
*
* These transactions deal with short positions and options, which are
* not supported at all by KMyMoney. They will be ignored for now.
* There may be a way to hack around this, by creating a new security
* "IBM_Short".
*
*************************************************************************/
/*
D1/21' 5
NShtSell
YIBM
I92.38
Q100
U9,238.00
T9,238.00
^
*/
/*
D1/28' 5
NCvrShrt
YIBM
I92.89
Q100
U9,339.00
T9,339.00
O50.00
^
*/
/*
D6/ 1' 5
NVest
YIBM Option
Q20
^
*/
/*
D6/ 8' 5
NExercise
YIBM Option
I60.952381
Q20
MFrom IBM Option Grant 6/1/2004
^
*/
/*
D6/ 1'14
NExpire
YIBM Option
Q5
^
*/
/*************************************************************************
*
* These transactions do not have an associated investment ("Y" field)
* so presumably they are only valid for the cash account. Once I
* understand how these are really implemented, they can probably be
* handled without much trouble.
*
*************************************************************************/
/*
D1/14' 5
NCash
U-100.00
T-100.00
LBank Chrg
^
*/
/*
D1/15' 5
NXOut
U500.00
T500.00
L[CU Savings]
$500.00
^
*/
/*
D1/28' 5
NXIn
U1,000.00
T1,000.00
L[CU Checking]
$1,000.00
^
*/
/*
D1/25' 5
NMargInt
U25.00
T25.00
^
*/
}
const QString MyMoneyQifReader::findOrCreateIncomeAccount(const QString& searchname)
{
QString result;
MyMoneyFile *file = MyMoneyFile::instance();
// First, try to find this account as an income account
MyMoneyAccount acc = file->income();
QStringList list = acc.accountList();
QStringList::ConstIterator it_accid = list.constBegin();
while (it_accid != list.constEnd()) {
acc = file->account(*it_accid);
if (acc.name() == searchname) {
result = *it_accid;
break;
}
++it_accid;
}
// If we did not find the account, now we must create one.
if (result.isEmpty()) {
MyMoneyAccount acc;
acc.setName(searchname);
- acc.setAccountType(MyMoneyAccount::Income);
+ acc.setAccountType(eMyMoney::Account::Income);
MyMoneyAccount income = file->income();
MyMoneyFileTransaction ft;
file->addAccount(acc, income);
ft.commit();
result = acc.id();
}
return result;
}
// TODO (Ace) Combine this and the previous function
const QString MyMoneyQifReader::findOrCreateExpenseAccount(const QString& searchname)
{
QString result;
MyMoneyFile *file = MyMoneyFile::instance();
// First, try to find this account as an income account
MyMoneyAccount acc = file->expense();
QStringList list = acc.accountList();
QStringList::ConstIterator it_accid = list.constBegin();
while (it_accid != list.constEnd()) {
acc = file->account(*it_accid);
if (acc.name() == searchname) {
result = *it_accid;
break;
}
++it_accid;
}
// If we did not find the account, now we must create one.
if (result.isEmpty()) {
MyMoneyAccount acc;
acc.setName(searchname);
- acc.setAccountType(MyMoneyAccount::Expense);
+ acc.setAccountType(eMyMoney::Account::Expense);
MyMoneyFileTransaction ft;
MyMoneyAccount expense = file->expense();
file->addAccount(acc, expense);
ft.commit();
result = acc.id();
}
return result;
}
const QString MyMoneyQifReader::processAccountEntry(bool resetAccountId)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount account;
QString tmp;
account.setName(extractLine('N'));
// qDebug("Process account '%s'", account.name().data());
account.setDescription(extractLine('D'));
tmp = extractLine('$');
if (tmp.length() > 0)
account.setValue("lastStatementBalance", tmp);
tmp = extractLine('/');
if (tmp.length() > 0)
account.setValue("lastStatementDate", m_qifProfile.date(tmp).toString("yyyy-MM-dd"));
QifEntryTypeE transactionType = EntryTransaction;
QString type = extractLine('T').toLower().remove(QRegExp("\\s+"));
if (type == m_qifProfile.profileType().toLower().remove(QRegExp("\\s+"))) {
- account.setAccountType(MyMoneyAccount::Checkings);
+ account.setAccountType(eMyMoney::Account::Checkings);
} else if (type == "ccard" || type == "creditcard") {
- account.setAccountType(MyMoneyAccount::CreditCard);
+ account.setAccountType(eMyMoney::Account::CreditCard);
} else if (type == "cash") {
- account.setAccountType(MyMoneyAccount::Cash);
+ account.setAccountType(eMyMoney::Account::Cash);
} else if (type == "otha") {
- account.setAccountType(MyMoneyAccount::Asset);
+ account.setAccountType(eMyMoney::Account::Asset);
} else if (type == "othl") {
- account.setAccountType(MyMoneyAccount::Liability);
+ account.setAccountType(eMyMoney::Account::Liability);
} else if (type == "invst" || type == "port") {
- account.setAccountType(MyMoneyAccount::Investment);
+ account.setAccountType(eMyMoney::Account::Investment);
transactionType = EntryInvestmentTransaction;
} else if (type == "mutual") { // stock account w/o umbrella investment account
- account.setAccountType(MyMoneyAccount::Stock);
+ account.setAccountType(eMyMoney::Account::Stock);
transactionType = EntryInvestmentTransaction;
} else if (type == "unknown") {
// don't do anything with the type, leave it unknown
} else {
- account.setAccountType(MyMoneyAccount::Checkings);
+ account.setAccountType(eMyMoney::Account::Checkings);
qDebug() << "Line " << m_linenumber << ": Unknown account type '" << type << "', checkings assumed";
}
// check if we can find the account already in the file
auto acc = findAccount(account, MyMoneyAccount());
if (acc.id().isEmpty()) {
// in case the account is not found by name and the type is
// unknown, we have to assume something and create a checking account.
// this might be wrong, but we have no choice at this point.
- if (account.accountType() == MyMoneyAccount::UnknownAccountType)
- account.setAccountType(MyMoneyAccount::Checkings);
+ if (account.accountType() == eMyMoney::Account::Unknown)
+ account.setAccountType(eMyMoney::Account::Checkings);
MyMoneyAccount parentAccount;
MyMoneyAccount brokerage;
// in case it's a stock account, we need to setup a fix investment account
if (account.isInvest()) {
acc.setName(i18n("%1 (Investment)", account.name())); // use the same name for the investment account
acc.setDescription(i18n("Autogenerated by QIF importer from type Mutual account entry"));
- acc.setAccountType(MyMoneyAccount::Investment);
+ acc.setAccountType(eMyMoney::Account::Investment);
parentAccount = file->asset();
file->createAccount(acc, parentAccount, brokerage, MyMoneyMoney());
parentAccount = acc;
qDebug("We still need to create the stock account in MyMoneyQifReader::processAccountEntry()");
} else {
// setup parent according the type of the account
switch (account.accountGroup()) {
- case MyMoneyAccount::Asset:
+ case eMyMoney::Account::Asset:
default:
parentAccount = file->asset();
break;
- case MyMoneyAccount::Liability:
+ case eMyMoney::Account::Liability:
parentAccount = file->liability();
break;
- case MyMoneyAccount::Equity:
+ case eMyMoney::Account::Equity:
parentAccount = file->equity();
break;
}
}
// investment accounts will receive a brokerage account, as KMyMoney
// currently does not allow to store funds in the investment account directly
// but only create it (not here, but later) if it is needed
- if (account.accountType() == MyMoneyAccount::Investment) {
+ if (account.accountType() == eMyMoney::Account::Investment) {
brokerage.setName(QString()); // brokerage name empty so account not created yet
- brokerage.setAccountType(MyMoneyAccount::Checkings);
+ brokerage.setAccountType(eMyMoney::Account::Checkings);
brokerage.setCurrencyId(MyMoneyFile::instance()->baseCurrency().id());
}
file->createAccount(account, parentAccount, brokerage, MyMoneyMoney());
acc = account;
// qDebug("Account created");
} else {
// qDebug("Existing account found");
}
if (resetAccountId) {
// possibly start a new statement
d->finishStatement();
m_account = acc;
d->st.m_accountId = m_account.id(); // needed here for account selection
d->transactionType = transactionType;
}
return acc.id();
}
void MyMoneyQifReader::setProgressCallback(void(*callback)(int, int, const QString&))
{
m_progressCallback = callback;
}
void MyMoneyQifReader::signalProgress(int current, int total, const QString& msg)
{
if (m_progressCallback != 0)
(*m_progressCallback)(current, total, msg);
}
void MyMoneyQifReader::processPriceEntry()
{
/*
!Type:Prices
"IBM",141 9/16,"10/23/98"
^
!Type:Prices
"GMW",21.28," 3/17' 5"
^
!Type:Prices
"GMW",71652181.001,"67/128/ 0"
^
Note that Quicken will often put in a price with a bogus date and number. We will ignore
prices with bogus dates. Hopefully that will catch all of these.
Also note that prices can be in fractional units, e.g. 141 9/16.
*/
QStringList::const_iterator it_line = m_qifEntry.constBegin();
// Make a price for each line
QRegExp priceExp("\"(.*)\",(.*),\"(.*)\"");
while (it_line != m_qifEntry.constEnd()) {
if (priceExp.indexIn(*it_line) != -1) {
MyMoneyStatement::Price price;
price.m_strSecurity = priceExp.cap(1);
QString pricestr = priceExp.cap(2);
QString datestr = priceExp.cap(3);
qDebug() << "Price:" << price.m_strSecurity << " / " << pricestr << " / " << datestr;
// Only add the price if the date is valid. If invalid, fail silently. See note above.
// Also require the price value to not have any slashes. Old prices will be something like
// "25 9/16", which we do not support. So we'll skip the price for now.
QDate date = m_qifProfile.date(datestr);
MyMoneyMoney rate(m_qifProfile.value('P', pricestr));
if (date.isValid() && !rate.isZero()) {
price.m_amount = rate;
price.m_date = date;
d->st.m_listPrices += price;
}
}
++it_line;
}
}
void MyMoneyQifReader::processSecurityEntry()
{
/*
!Type:Security
NVANGUARD 500 INDEX
SVFINX
TMutual Fund
^
*/
MyMoneyStatement::Security security;
security.m_strName = extractLine('N');
security.m_strSymbol = extractLine('S');
d->st.m_listSecurities += security;
}
diff --git a/kmymoney/plugins/qif/import/mymoneyqifreader.h b/kmymoney/plugins/qif/import/mymoneyqifreader.h
index 4c85b86c4..d9e489e2c 100644
--- a/kmymoney/plugins/qif/import/mymoneyqifreader.h
+++ b/kmymoney/plugins/qif/import/mymoneyqifreader.h
@@ -1,348 +1,348 @@
/***************************************************************************
mymoneyqifreader.h - description
-------------------
begin : Mon Jan 27 2003
copyright : (C) 2000-2003 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 MYMONEYQIFREADER_H
#define MYMONEYQIFREADER_H
// ----------------------------------------------------------------------------
// QT Headers
#include <QList>
#include <QObject>
#include <QString>
#include <QStringList>
#include <QByteArray>
#include <QUrl>
#include <QFile>
#include <QProcess>
// ----------------------------------------------------------------------------
// KDE Headers
// ----------------------------------------------------------------------------
// Project Headers
#include "mymoneyaccount.h"
#include "mymoneytransaction.h"
#include "../config/mymoneyqifprofile.h"
class MyMoneyFileTransaction;
class MyMoneyStatement;
class MyMoneyQifProfile;
/**
* @author Thomas Baumgart
*/
class MyMoneyQifReader : public QObject
{
Q_OBJECT
friend class Private;
private:
typedef enum {
EntryUnknown = 0,
EntryAccount,
EntryTransaction,
EntryCategory,
EntryMemorizedTransaction,
EntryInvestmentTransaction,
EntrySecurity,
EntryPrice,
EntryPayee,
EntryClass,
EntrySkip
} QifEntryTypeE;
struct qSplit {
QString m_strCategoryName;
QString m_strMemo;
QString m_amount;
};
public:
MyMoneyQifReader();
~MyMoneyQifReader();
/**
* This method is used to store the filename into the object.
* The file should exist. If it does and an external filter
* program is specified with the current selected profile,
* the file is send through this filter and the result
* is stored in the m_tempFile file.
*
* @param url URL of the file to be imported
*/
void setURL(const QUrl &url);
/**
* This method is used to store the name of the profile into the object.
* The selected profile will be loaded if it exists. If an external
* filter program is specified with the current selected profile,
* the file is send through this filter and the result
* is stored in the m_tempFile file.
*
* @param name QString reference to the name of the profile
*/
void setProfile(const QString& name);
/**
* This method actually starts the import of data from the selected file
* into the MyMoney engine.
*
* This method also starts the user defined import filter program
* defined in the QIF profile. If none is defined, the file is read
* as is (actually the UNIX command 'cat -' is used as the filter).
*
* If data from the filter program is available, the slot
* slotReceivedDataFromFilter() will be called.
*
* Make sure to connect the signal importFinished() to detect when
* the import actually ended. Call the method finishImport() to clean
* things up and get the overall result of the import.
*
* @retval true the import was started successfully
* @retval false the import could not be started.
*/
bool startImport();
void setCategoryMapping(bool map);
inline const MyMoneyAccount& account() const {
return m_account;
};
void setProgressCallback(void(*callback)(int, int, const QString&));
private:
/**
* This method is used to update the progress information. It
* checks if an appropriate function is known and calls it.
*
* For a parameter description see KMyMoneyView::progressCallback().
*/
void signalProgress(int current, int total, const QString& = "");
/**
* This method scans a transaction contained in
* a QIF file formatted as an account record. This
* format is used by MS-Money. If the specific data
* is not found, then the data in the entry is treated
* as a transaction. In this case, the user will be asked to
* specify the account to which the transactions should be imported.
* The entry data is found in m_qifEntry.
*
- * @param accountType see MyMoneyAccount() for details. Defaults to MyMoneyAccount::Checkings
+ * @param accountType see MyMoneyAccount() for details. Defaults to eMyMoney::Account::Checkings
*/
- void processMSAccountEntry(const MyMoneyAccount::accountTypeE accountType = MyMoneyAccount::Checkings);
+ void processMSAccountEntry(const eMyMoney::Account accountType = eMyMoney::Account::Checkings);
/**
* This method scans the m_qifEntry object as a payee record specified by Quicken
*/
void processPayeeEntry();
/**
* This method scans the m_qifEntry object as an account record specified
* by Quicken. In case @p resetAccountId is @p true (the default), the
* global account id will be reset.
*
* The id of the account will be returned.
*/
const QString processAccountEntry(bool resetAccountId = true);
/**
* This method scans the m_qifEntry object as a category record specified
* by Quicken.
*/
void processCategoryEntry();
/**
* This method scans the m_qifEntry object as a transaction record specified
* by Quicken.
*/
void processTransactionEntry();
/**
* This method scans the m_qifEntry object as an investment transaction
* record specified by Quicken.
*/
void processInvestmentTransactionEntry();
/**
* This method scans the m_qifEntry object as a price record specified
* by Quicken.
*/
void processPriceEntry();
/**
* This method scans the m_qifEntry object as a security record specified
* by Quicken.
*/
void processSecurityEntry();
/**
* This method processes the lines previously collected in
* the member variable m_qifEntry. If further information
* by the user is required to process the entry it will
* be collected.
*/
void processQifEntry();
/**
* This method process a line starting with an exclamation mark
*/
void processQifSpecial(const QString& _line);
/**
* This method extracts the line beginning with the letter @p id
* from the lines contained in the QStringList object @p m_qifEntry.
* An empty QString is returned, if the line is not found.
*
* @param id QChar containing the letter to be found
* @param cnt return cnt'th of occurrence of id in lines. cnt defaults to 1.
*
* @return QString with the remainder of the line or empty if
* @p id is not found in @p lines
*/
const QString extractLine(const QChar& id, int cnt = 1);
/**
* This method examines each line in the QStringList object @p m_qifEntry,
* searching for split entries, which it extracts into a struct qSplit and
* stores all splits found in @p listqSplits .
*/
bool extractSplits(QList<qSplit>& listqSplits) const;
enum SelectCreateMode {
Create = 0,
Select
};
/**
* This method looks up the @p searchname account by name and returns its id
* if it was found. If it was not found, it creates a new income account using
* @p searchname as a name, and returns the id if the newly created account
*
* @param searchname The name of the account to find or create
* @return QString id of the found or created account
*/
static const QString findOrCreateIncomeAccount(const QString& searchname);
/**
* This method looks up the @p searchname account by name and returns its id
* if it was found. If it was not found, it creates a new expense account using
* @p searchname as a name, and returns the id if the newly created account
*
* @param searchname The name of the account to find or create
* @return QString id of the found or created account
*/
static const QString findOrCreateExpenseAccount(const QString& searchname);
/**
* This methods returns the account from the list of accounts identified by
* an account id or account name including an account hierachy.
*
* The parent account specifies from which account the search should be started.
* In case the parent account does not have an id, the method scans all top-level accounts.
*
* If the account is not found in the list of accounts, MyMoneyAccount() is returned.
*
* @param acc account to find
* @param parent parent account to search from
* @retval found MyMoneyAccount account instance
* @retval MyMoneyAccount() if not found
*/
const MyMoneyAccount& findAccount(const MyMoneyAccount& acc, const MyMoneyAccount& parent) const;
/**
* This method returns the account id for a given account @a name. In
* case @a name references an investment account and @a useBrokerage is @a true
* (the default), the id of the corresponding brokerage account will be
* returned. In case an account does not exist, it will be created.
*/
const QString transferAccount(const QString& name, bool useBrokerage = true);
// void processQifLine();
- void createOpeningBalance(MyMoneyAccount::_accountTypeE accType = MyMoneyAccount::Checkings);
+ void createOpeningBalance(eMyMoney::Account accType = eMyMoney::Account::Checkings);
signals:
void statementsReady(QList<MyMoneyStatement> &);
private slots:
void slotSendDataToFilter();
void slotReceivedDataFromFilter();
void slotReceivedErrorFromFilter();
void slotProcessData();
/**
* This slot is used to be informed about the end of the filtering process.
* It emits the signal importFinished()
*/
void slotImportFinished();
private:
void parseReceivedData(const QByteArray& data);
/// \internal d-pointer class.
class Private;
/// \internal d-pointer instance.
Private* const d;
QProcess m_filter;
QString m_filename;
QUrl m_url;
MyMoneyQifProfile m_qifProfile;
MyMoneyAccount m_account;
unsigned long m_transactionsSkipped;
unsigned long m_transactionsProcessed;
QStringList m_dontAskAgain;
QMap<QString, QString> m_accountTranslation;
QMap<QString, QString> m_investmentMap;
QFile *m_file;
char m_buffer[1024];
QByteArray m_lineBuffer;
QStringList m_qifEntry;
int m_extractedLine;
QString m_qifLine;
QStringList m_qifLines;
QifEntryTypeE m_entryType;
bool m_skipAccount;
bool m_processingData;
bool m_userAbort;
bool m_autoCreatePayee;
unsigned long m_pos;
unsigned m_linenumber;
bool m_warnedInvestment;
bool m_warnedSecurity;
bool m_warnedPrice;
QList<MyMoneyTransaction> m_transactionCache;
QList<QByteArray> m_data;
void (*m_progressCallback)(int, int, const QString&);
MyMoneyFileTransaction* m_ft;
};
#endif
diff --git a/kmymoney/plugins/reconciliationreport/reconciliationreport.cpp b/kmymoney/plugins/reconciliationreport/reconciliationreport.cpp
index 69b0b7dd1..31b09841e 100644
--- a/kmymoney/plugins/reconciliationreport/reconciliationreport.cpp
+++ b/kmymoney/plugins/reconciliationreport/reconciliationreport.cpp
@@ -1,323 +1,323 @@
/***************************************************************************
* Copyright 2009 Cristian Onet onet.cristian@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) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 of the license. *
* *
* 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 <http://www.gnu.org/licenses/> *
***************************************************************************/
#include "reconciliationreport.h"
//! @todo remove
#include <QDebug>
#include <QPointer>
#include <QUrl>
// KDE includes
#include <KColorScheme>
#include <KLocalizedString>
// KMyMoney includes
#include "mymoneyfile.h"
#include "mymoneypayee.h"
#include "mymoneysecurity.h"
#include "mymoneysplit.h"
#include "mymoneytransaction.h"
#include "mymoneytransactionfilter.h"
#include "mymoneyutils.h"
#include "viewinterface.h"
#include "kreconciliationreportdlg.h"
KMMReconciliationReportPlugin::KMMReconciliationReportPlugin()
: KMyMoneyPlugin::Plugin(nullptr, "Reconciliation report"/*must be the same as X-KDE-PluginInfo-Name*/)
{
}
void KMMReconciliationReportPlugin::plug()
{
connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::accountReconciled, this, &KMMReconciliationReportPlugin::slotGenerateReconciliationReport);
qDebug() << "Connect was done" << viewInterface();
}
void KMMReconciliationReportPlugin::unplug()
{
disconnect(viewInterface(), &KMyMoneyPlugin::ViewInterface::accountReconciled, this, &KMMReconciliationReportPlugin::slotGenerateReconciliationReport);
}
void KMMReconciliationReportPlugin::slotGenerateReconciliationReport(const MyMoneyAccount& account, const QDate& date, const MyMoneyMoney& startingBalance, const MyMoneyMoney& endingBalance, const QList<QPair<MyMoneyTransaction, MyMoneySplit> >& transactionList)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneySecurity currency = file->currency(account.currencyId());
QString filename;
if (!MyMoneyFile::instance()->value("reportstylesheet").isEmpty())
filename = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("html/%1").arg(MyMoneyFile::instance()->value("reportstylesheet")));
if (filename.isEmpty())
filename = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "html/kmymoney.css");
QString header = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n") +
QString("<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"%1\">").arg(QUrl::fromLocalFile(filename).url());
header += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />";
QColor tcolor = KColorScheme(QPalette::Active).foreground(KColorScheme::NormalText).color();
QString css;
css += "<style type=\"text/css\">\n<!--\n"
+ QString(".row-even, .item0 { background-color: %1; color: %2 }\n")
.arg((KColorScheme(QPalette::Normal).background(KColorScheme::AlternateBackground).color()).name(), tcolor.name())
+ QString(".row-odd, .item1 { background-color: %1; color: %2 }\n")
.arg((KColorScheme(QPalette::Normal).background(KColorScheme::NormalBackground).color()).name(), tcolor.name())
+ "-->\n</style>\n";
header += css;
header += "</head><body>\n";
QString footer = "</body></html>\n";
MyMoneyMoney clearedBalance = startingBalance;
MyMoneyMoney clearedDepositAmount, clearedPaymentAmount;
int clearedDeposits = 0;
int clearedPayments = 0;
MyMoneyMoney outstandingDepositAmount, outstandingPaymentAmount;
int outstandingDeposits = 0;
int outstandingPayments = 0;
QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
for (it = transactionList.begin(); it != transactionList.end(); ++it) {
// if this split is a stock split, we can't just add the amount of shares
if ((*it).second.reconcileFlag() == MyMoneySplit::NotReconciled) {
if ((*it).second.shares().isNegative()) {
outstandingPayments++;
outstandingPaymentAmount += (*it).second.shares();
} else {
outstandingDeposits++;
outstandingDepositAmount += (*it).second.shares();
}
} else {
if ((*it).second.shares().isNegative()) {
clearedPayments++;
clearedPaymentAmount += (*it).second.shares();
} else {
clearedDeposits++;
clearedDepositAmount += (*it).second.shares();
}
clearedBalance += (*it).second.shares();
}
}
QString reportName = i18n("Reconciliation report of account %1", account.name());
QString report = QString("<h2 class=\"report\">%1</h2>\n").arg(reportName);
report += QString("<div class=\"subtitle\">");
report += QString("%1").arg(QLocale().toString(date, QLocale::ShortFormat));
report += QString("</div>\n");
report += QString("<div class=\"gap\">&nbsp;</div>\n");
report += QString("<div class=\"subtitle\">");
report += i18n("All values shown in %1", file->baseCurrency().name());
report += QString("</div>\n");
report += QString("<div class=\"gap\">&nbsp;</div>\n");
report += "<table class=\"report\">\n<thead><tr class=\"itemheader\">";
report += "<th>" + i18n("Summary") + "</th>";
report += "</tr></thead>\n";
// row 1
report += "<tr class=\"row-odd\"><td class=\"left\">";
report += i18n("Starting balance on bank statement");
report += "</td><td>";
report += MyMoneyUtils::formatMoney(startingBalance, currency);
report += "</td></tr>";
// row 2
report += "<tr class=\"row-even\"><td class=\"left\">";
report += i18np("%1 cleared payment", "%1 cleared payments in total", clearedPayments);
report += "</td><td>";
report += MyMoneyUtils::formatMoney(clearedPaymentAmount, currency);
report += "</td></tr>";
// row 3
report += "<tr class=\"row-odd\"><td class=\"left\">";
report += i18np("%1 cleared deposit", "%1 cleared deposits in total", clearedDeposits);
report += "</td><td>";
report += MyMoneyUtils::formatMoney(clearedDepositAmount, currency);
report += "</td></tr>";
// row 4
report += "<tr class=\"row-even\"><td class=\"left\">";
report += i18n("Ending balance on bank statement");
report += "</td><td>";
report += MyMoneyUtils::formatMoney(endingBalance, currency);
report += "</td></tr>";
// separator
report += "<tr class=\"spacer\"><td colspan=\"2\"> </td></tr>";
// row 5
report += "<tr class=\"row-odd\"><td class=\"left\">";
report += i18n("Cleared balance");
report += "</td><td>";
report += MyMoneyUtils::formatMoney(clearedBalance, currency);
report += "</td></tr>";
// row 6
report += "<tr class=\"row-even\"><td class=\"left\">";
report += i18np("%1 outstanding payment", "%1 outstanding payments in total", outstandingPayments);
report += "</td><td>";
report += MyMoneyUtils::formatMoney(outstandingPaymentAmount, currency);
report += "</td></tr>";
// row 7
report += "<tr class=\"row-odd\"><td class=\"left\">";
report += i18np("%1 outstanding deposit", "%1 outstanding deposits in total", outstandingDeposits);
report += "</td><td>";
report += MyMoneyUtils::formatMoney(outstandingDepositAmount, currency);
report += "</td></tr>";
// row 8
report += "<tr class=\"row-even\"><td class=\"left\">";
report += i18n("Register balance as of %1", QLocale().toString(date, QLocale::ShortFormat));
report += "</td><td>";
report += MyMoneyUtils::formatMoney(MyMoneyFile::instance()->balance(account.id(), date), currency);
report += "</td></tr>";
// retrieve list of all transactions after the reconciliation date that are not reconciled or cleared
QList<QPair<MyMoneyTransaction, MyMoneySplit> > afterTransactionList;
MyMoneyTransactionFilter filter(account.id());
- filter.addState(MyMoneyTransactionFilter::cleared);
- filter.addState(MyMoneyTransactionFilter::notReconciled);
+ filter.addState((int)eMyMoney::TransactionFilter::State::Cleared);
+ filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled);
filter.setDateFilter(date.addDays(1), QDate());
filter.setConsiderCategory(false);
filter.setReportAllSplits(true);
file->transactionList(afterTransactionList, filter);
MyMoneyMoney afterDepositAmount, afterPaymentAmount;
int afterDeposits = 0;
int afterPayments = 0;
for (it = afterTransactionList.constBegin(); it != afterTransactionList.constEnd(); ++it) {
// if this split is a stock split, we can't just add the amount of shares
if ((*it).second.reconcileFlag() == MyMoneySplit::NotReconciled) {
if ((*it).second.shares().isNegative()) {
afterPayments++;
afterPaymentAmount += (*it).second.shares();
} else {
afterDeposits++;
afterDepositAmount += (*it).second.shares();
}
}
}
// row 9
report += "<tr class=\"row-odd\"><td class=\"left\">";
report += i18np("%1 payment after %2", "%1 payments after %2", afterPayments, QLocale().toString(date, QLocale::ShortFormat));
report += "</td><td>";
report += MyMoneyUtils::formatMoney(afterPaymentAmount, currency);
report += "</td></tr>";
// row 10
report += "<tr class=\"row-even\"><td class=\"left\">";
report += i18np("%1 deposit after %2", "%1 deposits after %2", afterDeposits, QLocale().toString(date, QLocale::ShortFormat));
report += "</td><td>";
report += MyMoneyUtils::formatMoney(afterDepositAmount, currency);
report += "</td></tr>";
// row 11
report += "<tr class=\"row-odd\"><td class=\"left\">";
report += i18n("Register ending balance");
report += "</td><td>";
report += MyMoneyUtils::formatMoney(MyMoneyFile::instance()->balance(account.id()), currency);
report += "</td></tr>";
// end of the table
report += "</table>\n";
QString detailsTableHeader;
detailsTableHeader += "<table class=\"report\">\n<thead><tr class=\"itemheader\">";
detailsTableHeader += "<th>" + i18n("Date") + "</th>";
detailsTableHeader += "<th>" + i18n("Number") + "</th>";
detailsTableHeader += "<th>" + i18n("Payee") + "</th>";
detailsTableHeader += "<th>" + i18n("Memo") + "</th>";
detailsTableHeader += "<th>" + i18n("Category") + "</th>";
detailsTableHeader += "<th>" + i18n("Amount") + "</th>";
detailsTableHeader += "</tr></thead>\n";
QString detailsReport = QString("<h2 class=\"report\">%1</h2>\n").arg(i18n("Outstanding payments"));
detailsReport += detailsTableHeader;
int index = 0;
for (it = transactionList.begin(); it != transactionList.end(); ++it) {
if ((*it).second.reconcileFlag() == MyMoneySplit::NotReconciled && (*it).second.shares().isNegative()) {
QList<MyMoneySplit>::const_iterator it_s;
QString category;
for (it_s = (*it).first.splits().begin(); it_s != (*it).first.splits().end(); ++it_s) {
if ((*it_s).accountId() != account.id()) {
if (!category.isEmpty())
category += ", "; // this is a split transaction
category = file->account((*it_s).accountId()).name();
}
}
detailsReport += QString("<tr class=\"%1\"><td>").arg((index++ % 2 == 1) ? "row-odd" : "row-even");
detailsReport += QString("%1").arg(QLocale().toString((*it).first.entryDate(), QLocale::ShortFormat));
detailsReport += "</td><td>";
detailsReport += QString("%1").arg((*it).second.number());
detailsReport += "</td><td>";
detailsReport += QString("%1").arg(file->payee((*it).second.payeeId()).name());
detailsReport += "</td><td>";
detailsReport += QString("%1").arg((*it).first.memo());
detailsReport += "</td><td>";
detailsReport += QString("%1").arg(category);
detailsReport += "</td><td>";
detailsReport += QString("%1").arg(MyMoneyUtils::formatMoney((*it).second.shares(), file->currency(account.currencyId())));
detailsReport += "</td></tr>";
}
}
detailsReport += "<tr class=\"sectionfooter\">";
detailsReport += QString("<td class=\"left1\" colspan=\"5\">%1</td><td>%2</td></tr>").arg(i18np("One outstanding payment of", "Total of %1 outstanding payments amounting to", outstandingPayments)).arg(MyMoneyUtils::formatMoney(outstandingPaymentAmount, currency));
detailsReport += "</table>\n";
detailsReport += QString("<h2 class=\"report\">%1</h2>\n").arg(i18n("Outstanding deposits"));
detailsReport += detailsTableHeader;
index = 0;
for (it = transactionList.begin(); it != transactionList.end(); ++it) {
if ((*it).second.reconcileFlag() == MyMoneySplit::NotReconciled && !(*it).second.shares().isNegative()) {
QList<MyMoneySplit>::const_iterator it_s;
QString category;
for (it_s = (*it).first.splits().begin(); it_s != (*it).first.splits().end(); ++it_s) {
if ((*it_s).accountId() != account.id()) {
if (!category.isEmpty())
category += ", "; // this is a split transaction
category = file->account((*it_s).accountId()).name();
}
}
detailsReport += QString("<tr class=\"%1\"><td>").arg((index++ % 2 == 1) ? "row-odd" : "row-even")
+ QString("%1").arg(QLocale().toString((*it).first.entryDate(), QLocale::ShortFormat))
+ "</td><td>"
+ QString("%1").arg((*it).second.number())
+ "</td><td>"
+ QString("%1").arg(file->payee((*it).second.payeeId()).name())
+ "</td><td>"
+ QString("%1").arg((*it).first.memo())
+ "</td><td>"
+ QString("%1").arg(category)
+ "</td><td>"
+ QString("%1").arg(MyMoneyUtils::formatMoney((*it).second.shares(), file->currency(account.currencyId())))
+ "</td></tr>";
}
}
detailsReport += "<tr class=\"sectionfooter\">"
+ QString("<td class=\"left1\" colspan=\"5\">%1</td><td>%2</td></tr>").arg(i18np("One outstanding deposit of", "Total of %1 outstanding deposits amounting to", outstandingDeposits)).arg(MyMoneyUtils::formatMoney(outstandingDepositAmount, currency))
// end of the table
+ "</table>\n";
QPointer<KReportDlg> dlg = new KReportDlg(0, header + report + footer, header + detailsReport + footer);
dlg->exec();
delete dlg;
}
diff --git a/kmymoney/reports/kreportchartview.cpp b/kmymoney/reports/kreportchartview.cpp
index 444ff4099..f267110cf 100644
--- a/kmymoney/reports/kreportchartview.cpp
+++ b/kmymoney/reports/kreportchartview.cpp
@@ -1,740 +1,741 @@
/***************************************************************************
kreportchartview.cpp
-------------------
begin : Sun Aug 14 2005
copyright : (C) 2004-2005 by Ace Jones
email : <ace.j@hotpop.com>
(C) 2017 Łukasz Wojniłowicz <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 "kreportchartview.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QStandardItemModel>
#include <QFontDatabase>
#include <QtMath>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KColorScheme>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include <KChartBackgroundAttributes>
#include <KChartDataValueAttributes>
#include <KChartMarkerAttributes>
#include <KChartGridAttributes>
#include <KChartHeaderFooter>
#include <KChartLegend>
#include <KChartLineDiagram>
#include <KChartBarDiagram>
#include <KChartPieDiagram>
#include <KChartRingDiagram>
#include <KChartCartesianAxis>
#include <KChartFrameAttributes>
#include "kmymoneyglobalsettings.h"
#include <kbalanceaxis.h>
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
using namespace reports;
KReportChartView::KReportChartView(QWidget* parent) :
KChart::Chart(parent),
m_accountSeries(0),
m_seriesTotals(0),
m_numColumns(0),
m_skipZero(0),
m_backgroundBrush(KColorScheme(QPalette::Current).background()),
m_foregroundBrush(KColorScheme(QPalette::Current).foreground())
{
// ********************************************************************
// Set KMyMoney's Chart Parameter Defaults
// ********************************************************************
//Set the background obtained from the color scheme
BackgroundAttributes backAttr(backgroundAttributes());
backAttr.setBrush(m_backgroundBrush);
backAttr.setVisible(true);
setBackgroundAttributes(backAttr);
}
void KReportChartView::drawPivotChart(const PivotGrid &grid, const MyMoneyReport &config, int numberColumns, const QStringList& columnHeadings, const QList<ERowType>& rowTypeList, const QStringList& columnTypeHeaderList)
{
if (numberColumns == 0)
return;
//set the number of columns
setNumColumns(numberColumns);
//set skipZero
m_skipZero = config.isSkippingZero();
//remove existing headers
const HeaderFooterList hfList = headerFooters();
foreach (const auto hf, hfList)
delete hf;
//remove existing legends
const LegendList lgList = legends();
foreach (const auto lg, lgList)
delete lg;
//make sure the model is clear
m_model.clear();
const bool blocked = m_model.blockSignals(true); // don't emit dataChanged() signal during each drawPivotRowSet
//set the new header
HeaderFooter* header = new HeaderFooter;
header->setText(config.name());
header->setType(HeaderFooter::Header);
header->setPosition(Position::North);
TextAttributes headerTextAttr(header->textAttributes());
headerTextAttr.setPen(m_foregroundBrush.color());
header->setTextAttributes(headerTextAttr);
addHeaderFooter(header);
// whether to limit the chart to use series totals only. Used for reports which only
// show one dimension (pie).
setSeriesTotals(false);
// whether series (rows) are accounts (true) or months (false). This causes a lot
// of complexity in the charts. The problem is that circular reports work best with
// an account in a COLUMN, while line/bar prefer it in a ROW.
setAccountSeries(true);
switch (config.chartType()) {
case MyMoneyReport::eChartLine:
case MyMoneyReport::eChartBar:
case MyMoneyReport::eChartStackedBar: {
CartesianCoordinatePlane* cartesianPlane = new CartesianCoordinatePlane(this);
cartesianPlane->setAutoAdjustVerticalRangeToData(2);
cartesianPlane->setRubberBandZoomingEnabled(true);
replaceCoordinatePlane(cartesianPlane);
// set-up axis type
if (config.isLogYAxis())
cartesianPlane->setAxesCalcModeY(KChart::AbstractCoordinatePlane::Logarithmic);
else
cartesianPlane->setAxesCalcModeY(KChart::AbstractCoordinatePlane::Linear);
QLocale loc = locale();
// set-up grid
GridAttributes ga = cartesianPlane->gridAttributes(Qt::Vertical);
ga.setGridVisible(config.isChartCHGridLines());
ga.setGridStepWidth(config.isDataUserDefined() ? loc.toDouble(config.dataMajorTick()) : 0.0);
ga.setGridSubStepWidth(config.isDataUserDefined() ? loc.toDouble(config.dataMinorTick()) : 0.0);
cartesianPlane->setGridAttributes(Qt::Vertical, ga);
ga = cartesianPlane->gridAttributes(Qt::Horizontal);
ga.setGridVisible(config.isChartSVGridLines());
cartesianPlane->setGridAttributes(Qt::Horizontal, ga);
// set-up data range
cartesianPlane->setVerticalRange(qMakePair(config.isDataUserDefined() ? loc.toDouble(config.dataRangeStart()) : 0.0,
config.isDataUserDefined() ? loc.toDouble(config.dataRangeEnd()) : 0.0));
//set-up x axis
CartesianAxis *xAxis = new CartesianAxis();
xAxis->setPosition(CartesianAxis::Bottom);
xAxis->setTitleText(i18n("Time"));
TextAttributes xAxisTitleTextAttr(xAxis->titleTextAttributes());
xAxisTitleTextAttr.setMinimalFontSize(QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize());
xAxisTitleTextAttr.setPen(m_foregroundBrush.color());
xAxis->setTitleTextAttributes(xAxisTitleTextAttr);
TextAttributes xAxisTextAttr(xAxis->textAttributes());
xAxisTextAttr.setPen(m_foregroundBrush.color());
xAxis->setTextAttributes(xAxisTextAttr);
RulerAttributes xAxisRulerAttr(xAxis->rulerAttributes());
xAxisRulerAttr.setTickMarkPen(m_foregroundBrush.color());
xAxisRulerAttr.setShowRulerLine(true);
xAxis->setRulerAttributes(xAxisRulerAttr);
//set-up y axis
KBalanceAxis *yAxis = new KBalanceAxis();
yAxis->setPosition(CartesianAxis::Left);
if (config.isIncludingPrice())
yAxis->setTitleText(i18n("Price"));
else
yAxis->setTitleText(i18n("Balance"));
TextAttributes yAxisTitleTextAttr(yAxis->titleTextAttributes());
yAxisTitleTextAttr.setMinimalFontSize(QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize());
yAxisTitleTextAttr.setPen(m_foregroundBrush.color());
yAxis->setTitleTextAttributes(yAxisTitleTextAttr);
TextAttributes yAxisTextAttr(yAxis->textAttributes());
yAxisTextAttr.setPen(m_foregroundBrush.color());
yAxis->setTextAttributes(yAxisTextAttr);
RulerAttributes yAxisRulerAttr(yAxis->rulerAttributes());
yAxisRulerAttr.setTickMarkPen(m_foregroundBrush.color());
yAxisRulerAttr.setShowRulerLine(true);
yAxis->setRulerAttributes(yAxisRulerAttr);
switch (config.chartType()) {
case MyMoneyReport::eChartEnd:
case MyMoneyReport::eChartLine: {
KChart::LineDiagram* diagram = new KChart::LineDiagram(this, cartesianPlane);
if (config.isSkippingZero()) {
LineAttributes attributes = diagram->lineAttributes();
attributes.setMissingValuesPolicy(LineAttributes::MissingValuesAreBridged);
diagram->setLineAttributes(attributes);
}
cartesianPlane->replaceDiagram(diagram);
diagram->addAxis(xAxis);
diagram->addAxis(yAxis);
break;
}
case MyMoneyReport::eChartBar: {
KChart::BarDiagram* diagram = new KChart::BarDiagram(this, cartesianPlane);
cartesianPlane->replaceDiagram(diagram);
diagram->addAxis(xAxis);
diagram->addAxis(yAxis);
break;
}
case MyMoneyReport::eChartStackedBar: {
KChart::BarDiagram* diagram = new KChart::BarDiagram(this, cartesianPlane);
diagram->setType(BarDiagram::Stacked);
cartesianPlane->replaceDiagram(diagram);
diagram->addAxis(xAxis);
diagram->addAxis(yAxis);
break;
}
default:
break;
}
break;
}
case MyMoneyReport::eChartPie:
case MyMoneyReport::eChartRing:{
PolarCoordinatePlane* polarPlane = new PolarCoordinatePlane(this);
replaceCoordinatePlane(polarPlane);
// set-up grid
GridAttributes ga = polarPlane->gridAttributes(true);
ga.setGridVisible(config.isChartCHGridLines());
polarPlane->setGridAttributes(true, ga);
ga = polarPlane->gridAttributes(false);
ga.setGridVisible(config.isChartSVGridLines());
polarPlane->setGridAttributes(false, ga);
setAccountSeries(false);
switch (config.chartType()) {
case MyMoneyReport::eChartPie: {
KChart::PieDiagram* diagram = new KChart::PieDiagram(this, polarPlane);
polarPlane->replaceDiagram(diagram);
setSeriesTotals(true);
break;
}
case MyMoneyReport::eChartRing: {
KChart::RingDiagram* diagram = new KChart::RingDiagram(this, polarPlane);
polarPlane->replaceDiagram(diagram);
break;
}
default:
break;
}
break;
}
default: // no valid chart types
return;
}
//get the coordinate plane and the diagram for later use
AbstractCoordinatePlane* cPlane = coordinatePlane();
AbstractDiagram* planeDiagram = cPlane->diagram();
planeDiagram->setAntiAliasing(true);
//the palette - we set it here because it is a property of the diagram
switch (KMyMoneySettings::chartsPalette()) {
case 0:
planeDiagram->useDefaultColors();
break;
case 1:
planeDiagram->useRainbowColors();
break;
case 2:
default:
planeDiagram->useSubduedColors();
break;
}
int eBudgetDiffIdx = rowTypeList.indexOf(eBudgetDiff);
QList<ERowType> myRowTypeList = rowTypeList;
myRowTypeList.removeAt(eBudgetDiffIdx);
QStringList myColumnTypeHeaderList = columnTypeHeaderList;
myColumnTypeHeaderList.removeAt(eBudgetDiffIdx);
int myRowTypeListSize = myRowTypeList.size();
MyMoneyFile* file = MyMoneyFile::instance();
int precision = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction());
int rowNum = 0;
QStringList legendNames;
QMap<MyMoneyMoney, int> legendTotal;
switch (config.detailLevel()) {
case MyMoneyReport::eDetailNone:
case MyMoneyReport::eDetailAll: {
// iterate over outer groups
PivotGrid::const_iterator it_outergroup = grid.begin();
while (it_outergroup != grid.end()) {
// iterate over inner groups
PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
// iterate over accounts
PivotInnerGroup::const_iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
//Do not include investments accounts in the chart because they are merely container of stock and other accounts
- if (it_row.key().accountType() != MyMoneyAccount::Investment) {
+ if (it_row.key().accountType() != eMyMoney::Account::Investment) {
// get displayed precision
int currencyPrecision = precision;
int securityPrecision = precision;
if (!it_row.key().id().isEmpty()) {
const MyMoneyAccount acc = file->account(it_row.key().id());
if (acc.isInvest()) {
securityPrecision = file->currency(acc.currencyId()).pricePrecision();
// stock account isn't eveluated in currency, so take investment account instead
currencyPrecision = MyMoneyMoney::denomToPrec(file->account(acc.parentAccountId()).fraction());
} else
currencyPrecision = MyMoneyMoney::denomToPrec(acc.fraction());
}
// iterate row types
for (int i = 0 ; i < myRowTypeListSize; ++i) {
QString legendText;
//only show the column type in the header if there is more than one type
if (myRowTypeListSize > 1)
legendText = QString(myColumnTypeHeaderList.at(i) + QLatin1Literal(" - ") + it_row.key().name());
else
legendText = it_row.key().name();
//set the legend text
legendNames.append(legendText);
legendTotal.insertMulti(it_row.value().value(myRowTypeList.at(i)).m_total.abs(), rowNum);
precision = myRowTypeList.at(i) == ePrice ? securityPrecision : currencyPrecision;
//set the cell value and tooltip
rowNum = drawPivotGridRow(rowNum, it_row.value().value(myRowTypeList.at(i)),
config.isChartDataLabels() ? legendText : QString(),
0, numberColumns, precision);
}
}
++it_row;
}
++it_innergroup;
}
++it_outergroup;
}
}
break;
case MyMoneyReport::eDetailTop: {
// iterate over outer groups
PivotGrid::const_iterator it_outergroup = grid.begin();
while (it_outergroup != grid.end()) {
// iterate over inner groups
PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
// iterate row types
for (int i = 0 ; i < myRowTypeListSize; ++i) {
QString legendText;
//only show the column type in the header if there is more than one type
if (myRowTypeListSize > 1)
legendText = QString(myColumnTypeHeaderList.at(i) + QLatin1Literal(" - ") + it_innergroup.key());
else
legendText = it_innergroup.key();
//set the legend text
legendNames.append(legendText);
legendTotal.insertMulti((*it_innergroup).m_total.value(myRowTypeList.at(i)).m_total.abs(), rowNum);
//set the cell value and tooltip
rowNum = drawPivotGridRow(rowNum, (*it_innergroup).m_total.value(myRowTypeList.at(i)),
config.isChartDataLabels() ? legendText : QString(),
0, numberColumns, precision);
}
++it_innergroup;
}
++it_outergroup;
}
}
break;
case MyMoneyReport::eDetailGroup: {
// iterate over outer groups
PivotGrid::const_iterator it_outergroup = grid.begin();
while (it_outergroup != grid.end()) {
// iterate row types
for (int i = 0 ; i < myRowTypeListSize; ++i) {
QString legendText;
//only show the column type in the header if there is more than one type
if (myRowTypeListSize > 1)
legendText = QString(myColumnTypeHeaderList.at(i) + QLatin1Literal(" - ") + it_outergroup.key());
else
legendText = it_outergroup.key();
//set the legend text
legendNames.append(legendText);
legendTotal.insertMulti((*it_outergroup).m_total.value(myRowTypeList.at(i)).m_total.abs(), rowNum);
//set the cell value and tooltip
rowNum = drawPivotGridRow(rowNum, (*it_outergroup).m_total.value(myRowTypeList.at(i)),
config.isChartDataLabels() ? legendText : QString(),
0, numberColumns, precision);
}
++it_outergroup;
}
//if selected, show totals too
if (config.isShowingRowTotals()) {
// iterate row types
for (int i = 0 ; i < myRowTypeListSize; ++i) {
QString legendText;
//only show the column type in the header if there is more than one type
if (myRowTypeListSize > 1)
legendText = QString(myColumnTypeHeaderList.at(i) + QLatin1Literal(" - ") + i18nc("Total balance", "Total"));
else
legendText = QString(i18nc("Total balance", "Total"));
//set the legend text
legendNames.append(legendText);
legendTotal.insertMulti(grid.m_total.value(myRowTypeList.at(i)).m_total.abs(), rowNum);
//set the cell value and tooltip
rowNum = drawPivotGridRow(rowNum, grid.m_total.value(myRowTypeList.at(i)),
config.isChartDataLabels() ? legendText : QString(),
0, numberColumns, precision);
}
}
}
break;
case MyMoneyReport::eDetailTotal: {
// iterate row types
for (int i = 0 ; i < myRowTypeListSize; ++i) {
QString legendText;
//only show the column type in the header if there is more than one type
if (myRowTypeListSize > 1)
legendText = QString(myColumnTypeHeaderList.at(i) + QLatin1Literal(" - ") + i18nc("Total balance", "Total"));
else
legendText = QString(i18nc("Total balance", "Total"));
//set the legend text
legendNames.append(legendText);
legendTotal.insertMulti(grid.m_total.value(myRowTypeList.at(i)).m_total.abs(), rowNum);
//set the cell value and tooltip
if (config.isMixedTime()) {
if (myRowTypeList.at(i) == eActual)
rowNum = drawPivotGridRow(rowNum, grid.m_total.value(myRowTypeList.at(i)),
config.isChartDataLabels() ? legendText : QString(),
0, config.currentDateColumn(), precision);
else if (myRowTypeList.at(i)== eForecast) {
rowNum = drawPivotGridRow(rowNum, grid.m_total.value(myRowTypeList.at(i)),
config.isChartDataLabels() ? legendText : QString(),
config.currentDateColumn(), numberColumns - config.currentDateColumn(), precision);
} else
rowNum = drawPivotGridRow(rowNum, grid.m_total.value(myRowTypeList.at(i)),
config.isChartDataLabels() ? legendText : QString(),
0, numberColumns, precision);
} else
rowNum = drawPivotGridRow(rowNum, grid.m_total.value(myRowTypeList.at(i)),
config.isChartDataLabels() ? legendText : QString(),
0, numberColumns, precision);
}
}
break;
default:
case MyMoneyReport::eDetailEnd:
return;
}
auto legendRows = legendTotal.values(); // list of legend rows sorted ascending by total value
for (auto i = 0; i < legendRows.count(); ++i) {
const auto ixRow = legendRows.count() - 1 - i; // take row with the highest total value i.e. form the bottom
const auto row = legendRows.at(ixRow);
if ( row != i) { // if legend isn't sorted by total value, then rearrange model
if ((accountSeries() && !seriesTotals()) ||
(seriesTotals() && !accountSeries()))
m_model.insertColumn(i, m_model.takeColumn(row));
else
m_model.insertRow(i, m_model.takeRow(row));
for (auto j = i; j < ixRow; ++j) { // fix invalid indexes after above move operation
if (legendRows.at(j) < row)
++legendRows[j];
}
legendRows[ixRow] = i;
legendNames.move(row, i);
}
}
// Set up X axis labels (ie "abscissa" to use the technical term)
if (accountSeries()) { // if not, we will set these up while putting in the chart values.
QStringList xLabels;
foreach (const auto colHeading, columnHeadings)
xLabels.append(QString(colHeading).replace(QLatin1String("&nbsp;"), QLatin1String(" ")));
m_model.setVerticalHeaderLabels(xLabels);
}
m_model.setHorizontalHeaderLabels(legendNames);
// set line width for line chart
if (config.chartType() == MyMoneyReport::eChartLine) {
AttributesModel* diagramAttributes = planeDiagram->attributesModel();
int penWidth = config.chartLineWidth();
for (int i = 0 ; i < rowNum ; ++i) {
QPen pen = diagramAttributes->headerData(i, Qt::Horizontal, DatasetPenRole).value< QPen >();
pen.setWidth(penWidth);
m_model.setHeaderData(i, Qt::Horizontal, qVariantFromValue(pen), DatasetPenRole);
}
}
// the legend is needed only if at least two data sets are rendered
if (qMin(static_cast<int>(KMyMoneyGlobalSettings::maximumLegendItems()), rowNum) > 1) {
//the legend will be used later
Legend* legend = new Legend(planeDiagram, this);
legend->setTitleText(i18nc("Chart legend title", "Legend"));
//set the legend basic attributes
//this is done after adding the legend because the values are overridden when adding the legend to the chart
const auto maxLegendItems = KMyMoneyGlobalSettings::maximumLegendItems();
auto legendItems = legendNames.count();
auto i = 0;
while (legendItems > maxLegendItems) {
legend->setDatasetHidden(legendRows.at(i++), true);
--legendItems;
}
legend->setUseAutomaticMarkerSize(false);
FrameAttributes legendFrameAttr(legend->frameAttributes());
legendFrameAttr.setPen(m_foregroundBrush.color());
// leave some space between the content and the frame
legendFrameAttr.setPadding(2);
legend->setFrameAttributes(legendFrameAttr);
legend->setPosition(Position::East);
legend->setTextAlignment(Qt::AlignLeft);
if (config.isChartDataLabels())
legend->setLegendStyle(KChart::Legend::MarkersAndLines);
else
legend->setLegendStyle(KChart::Legend::LinesOnly);
replaceLegend(legend);
// set the text attributes after calling replaceLegend() otherwise fon sizes will get overwritten
qreal generalFontSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSizeF();
if (generalFontSize == -1)
generalFontSize = 8; // this is a fallback if the fontsize was specified in pixels
TextAttributes legendTextAttr(legend->textAttributes());
legendTextAttr.setPen(m_foregroundBrush.color());
legendTextAttr.setFontSize(KChart::Measure(generalFontSize, KChartEnums::MeasureCalculationModeAbsolute));
legend->setTextAttributes(legendTextAttr);
TextAttributes legendTitleTextAttr(legend->titleTextAttributes());
legendTitleTextAttr.setPen(m_foregroundBrush.color());
legendTitleTextAttr.setFontSize(KChart::Measure(generalFontSize + 4, KChartEnums::MeasureCalculationModeAbsolute));
legend->setTitleTextAttributes(legendTitleTextAttr);
}
//set data value attributes
//make sure to show only the required number of fractional digits on the labels of the graph
DataValueAttributes dataValueAttr(planeDiagram->dataValueAttributes());
MarkerAttributes markerAttr(dataValueAttr.markerAttributes());
markerAttr.setVisible(true);
markerAttr.setMarkerStyle(MarkerAttributes::MarkerCircle);
dataValueAttr.setMarkerAttributes(markerAttr);
TextAttributes dataValueTextAttr(dataValueAttr.textAttributes());
dataValueTextAttr.setPen(m_foregroundBrush.color());
dataValueAttr.setTextAttributes(dataValueTextAttr);
m_precision = config.yLabelsPrecision();
dataValueAttr.setDecimalDigits(config.yLabelsPrecision());
dataValueAttr.setVisible(config.isChartDataLabels());
planeDiagram->setDataValueAttributes(dataValueAttr);
planeDiagram->setAllowOverlappingDataValueTexts(true);
m_model.blockSignals(blocked); // reenable dataChanged() signal
//assign model to the diagram
planeDiagram->setModel(&m_model);
// connect needLayoutPlanes, so dimension of chart can be known, so custom Y labels can be generated
connect(cPlane, SIGNAL(needLayoutPlanes()), this, SLOT(slotNeedUpdate()));
}
void KReportChartView::slotNeedUpdate()
{
disconnect(coordinatePlane(), SIGNAL(needLayoutPlanes()), this, SLOT(slotNeedUpdate())); // this won't cause hang-up in KReportsView::slotConfigure
QList<DataDimension> grids = coordinatePlane()->gridDimensionsList();
if (grids.isEmpty()) // ring and pie charts have no dimensions
return;
if (grids.at(1).stepWidth == 0) // no labels?
return;
QLocale loc = locale();
QChar separator = loc.groupSeparator();
QChar decimalPoint = loc.decimalPoint();
QStringList labels;
if (m_precision > 10 || m_precision <= 0) // assure that conversion through QLocale::toString() will always work
m_precision = 1;
CartesianCoordinatePlane* cartesianplane = qobject_cast<CartesianCoordinatePlane*>(coordinatePlane());
if (cartesianplane) {
if (cartesianplane->axesCalcModeY() == KChart::AbstractCoordinatePlane::Logarithmic) {
qreal labelValue = qFloor(log10(grids.at(1).start)); // first label is 10 to power of labelValue
int labelCount = qFloor(log10(grids.at(1).end)) - qFloor(log10(grids.at(1).start)) + 1;
for (auto i = 0; i < labelCount; ++i) {
labels.append(loc.toString(qPow(10.0, labelValue), 'f', m_precision).remove(separator).remove(QRegularExpression("0+$")).remove(QRegularExpression("\\" + decimalPoint + "$")));
++labelValue; // next label is 10 to power of previous exponent + 1
}
} else {
qreal labelValue = grids.at(1).start; // first label is start value
qreal step = grids.at(1).stepWidth;
int labelCount = qFloor((grids.at(1).end - grids.at(1).start) / grids.at(1).stepWidth) + 1;
for (auto i = 0; i < labelCount; ++i) {
labels.append(loc.toString(labelValue, 'f', m_precision).remove(separator).remove(QRegularExpression("0+$")).remove(QRegularExpression("\\" + decimalPoint + "$")));
labelValue += step; // next label is previous value + step value
}
}
} else
return; // nothing but cartesian plane is handled
KChart::LineDiagram* lineDiagram = qobject_cast<LineDiagram*>(coordinatePlane()->diagram());
if (lineDiagram)
lineDiagram->axes().at(1)->setLabels(labels);
KChart::BarDiagram* barDiagram = qobject_cast<BarDiagram*>(coordinatePlane()->diagram());
if (barDiagram)
barDiagram->axes().at(1)->setLabels(labels);
}
int KReportChartView::drawPivotGridRow(int rowNum, const PivotGridRow& gridRow, const QString& legendText, const int startColumn, const int columnsToDraw, const int precision)
{
// Columns
QString toolTip = QString(QLatin1Literal("<h2>%1</h2><strong>%2</strong><br>")).arg(legendText);
bool isToolTip = !legendText.isEmpty();
if (seriesTotals()) {
QStandardItem* item = new QStandardItem();
double value = gridRow.m_total.toDouble();
item->setData(QVariant(value), Qt::DisplayRole);
if (isToolTip)
item->setToolTip(toolTip.arg(value, 0, 'f', precision));
//set the cell value
if (accountSeries()) {
m_model.insertRows(rowNum, 1);
m_model.setItem(rowNum, 0, item);
} else {
m_model.insertColumns(rowNum, 1);
m_model.setItem(0, rowNum, item);
}
} else {
QList<QStandardItem*> itemList;
for (int i = startColumn; i < columnsToDraw; ++i) {
QStandardItem* item = new QStandardItem();
if (!m_skipZero || !gridRow.at(i).isZero()) {
double value = gridRow.at(i).toDouble();
item->setData(QVariant(value), Qt::DisplayRole);
if (isToolTip)
item->setToolTip(toolTip.arg(value, 0, 'f', precision));
}
itemList.append(item);
}
if (accountSeries())
m_model.appendColumn(itemList);
else
m_model.appendRow(itemList);
}
return ++rowNum;
}
void KReportChartView::setDataCell(int row, int column, const double value, QString tip)
{
QMap<int, QVariant> data;
data.insert(Qt::DisplayRole, QVariant(value));
if (!tip.isEmpty())
data.insert(Qt::ToolTipRole, QVariant(tip));
const QModelIndex index = m_model.index(row, column);
m_model.setItemData(index, data);
}
/**
* Justifies the model, so that the given rows and columns fit into it.
*/
void KReportChartView::justifyModelSize(int rows, int columns)
{
const int currentRows = m_model.rowCount();
const int currentCols = m_model.columnCount();
if (currentCols < columns)
if (! m_model.insertColumns(currentCols, columns - currentCols))
qDebug() << "justifyModelSize: could not increase model size.";
if (currentRows < rows)
if (! m_model.insertRows(currentRows, rows - currentRows))
qDebug() << "justifyModelSize: could not increase model size.";
Q_ASSERT(m_model.rowCount() >= rows);
Q_ASSERT(m_model.columnCount() >= columns);
}
void KReportChartView::setLineWidth(const int lineWidth)
{
LineDiagram* lineDiagram = qobject_cast<LineDiagram*>(coordinatePlane()->diagram());
if (lineDiagram) {
QList <QPen> pens;
pens = lineDiagram->datasetPens();
for (int i = 0; i < pens.count(); ++i) {
pens[i].setWidth(lineWidth);
lineDiagram->setPen(i, pens.at(i));
}
}
}
void KReportChartView::drawLimitLine(const double limit)
{
if (coordinatePlane()->diagram()->datasetDimension() != 1)
return;
// temporarily disconnect the view from the model to aovid update of view on
// emission of the dataChanged() signal for each call of setDataCell().
// This speeds up the runtime of drawLimitLine() by a factor of
// approx. 60 on my box (1831ms vs. 31ms).
AbstractDiagram* planeDiagram = coordinatePlane()->diagram();
planeDiagram->setModel(0);
//we get the current number of rows and we add one after that
int row = m_model.rowCount();
justifyModelSize(m_numColumns, row + 1);
for (int col = 0; col < m_numColumns; ++col) {
setDataCell(col, row, limit);
}
planeDiagram->setModel(&m_model);
//TODO: add format to the line
}
void KReportChartView::removeLegend()
{
Legend* chartLegend = Chart::legend();
delete chartLegend;
}
diff --git a/kmymoney/reports/listtable.cpp b/kmymoney/reports/listtable.cpp
index 201a69d31..186a8c641 100644
--- a/kmymoney/reports/listtable.cpp
+++ b/kmymoney/reports/listtable.cpp
@@ -1,739 +1,742 @@
/***************************************************************************
listtable.cpp
-------------------
begin : Sat 28 jun 2008
copyright : (C) 2004-2005 by Ace Jones <acejones@users.sourceforge.net>
2008 by Alvaro Soliverez <asoliverez@gmail.com>
(C) 2017 Łukasz Wojniłowicz <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 "listtable.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
#include <QFile>
#include <QTextStream>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// KDE Includes
// This is just needed for i18n(). Once I figure out how to handle i18n
// without using this macro directly, I'll be freed of KDE dependency.
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneyaccount.h"
+#include "mymoneysecurity.h"
+#include "mymoneytransaction.h"
#include "mymoneyreport.h"
#include "kmymoneyglobalsettings.h"
namespace reports
{
QVector<ListTable::cellTypeE> ListTable::TableRow::m_sortCriteria;
// ****************************************************************************
//
// ListTable implementation
//
// ****************************************************************************
bool ListTable::TableRow::operator< (const TableRow& _compare) const
{
bool result = false;
foreach (const auto criterion, m_sortCriteria) {
if (this->operator[](criterion) < _compare[criterion]) {
result = true;
break;
} else if (this->operator[](criterion) > _compare[criterion]) {
break;
}
}
return result;
}
// needed for KDE < 3.2 implementation of qHeapSort
bool ListTable::TableRow::operator<= (const TableRow& _compare) const
{
return (!(_compare < *this));
}
bool ListTable::TableRow::operator== (const TableRow& _compare) const
{
return (!(*this < _compare) && !(_compare < *this));
}
bool ListTable::TableRow::operator> (const TableRow& _compare) const
{
return (_compare < *this);
}
/**
* TODO
*
* - Collapse 2- & 3- groups when they are identical
* - Way more test cases (especially splits & transfers)
* - Option to collapse splits
* - Option to exclude transfers
*
*/
ListTable::ListTable(const MyMoneyReport& _report):
ReportTable(_report)
{
}
void ListTable::render(QString& result, QString& csv) const
{
MyMoneyFile* file = MyMoneyFile::instance();
result.clear();
csv.clear();
// retrieve the configuration parameters from the report definition.
// the things that we care about for query reports are:
// how to group the rows, what columns to display, and what field
// to subtotal on
QList<cellTypeE> columns = m_columns;
if (!m_subtotal.isEmpty() && m_subtotal.count() == 1) // constructPerformanceRow has subtotal columns already in columns
columns.append(m_subtotal);
QList<cellTypeE> postcolumns = m_postcolumns;
if (!m_postcolumns.isEmpty()) // prevent creation of empty column
columns.append(postcolumns);
result.append(QLatin1String("<table class=\"report\">\n<thead><tr class=\"itemheader\">"));
//
// Table header
//
foreach (const auto cellType, columns) {
result.append(QString::fromLatin1("<th>%1</th>").arg(tableHeader(cellType)));
csv.append(tableHeader(cellType) + QLatin1Char(','));
}
csv.chop(1); // remove last ',' character
result.append(QLatin1String("</tr></thead>\n"));
csv.append(QLatin1Char('\n'));
// initialize group names to empty, so any group will have to display its header
QStringList prevGrpNames;
for (int i = 0; i < m_group.count(); ++i) {
prevGrpNames.append(QString());
}
//
// Rows
//
bool row_odd = true;
bool isLowestGroupTotal = true; // hack to inform whether to put separator line or not
// ***DV***
MyMoneyMoney startingBalance;
MyMoneyMoney balanceChange = MyMoneyMoney();
for (QList<TableRow>::ConstIterator it_row = m_rows.constBegin();
it_row != m_rows.constEnd();
++it_row) {
/* rank can be:
* 0 - opening balance
* 1 - major split of transaction
* 2 - minor split of transaction
* 3 - closing balance
* 4 - first totals row
* 5 - middle totals row
*/
const int rowRank = (*it_row).value(ctRank).toInt();
// detect whether any of groups changed and display new group header in that case
for (int i = 0; i < m_group.count(); ++i) {
QString curGrpName = (*it_row).value(m_group.at(i));
if (curGrpName.isEmpty()) // it could be grand total
continue;
if (prevGrpNames.at(i) != curGrpName) {
// group header of lowest group doesn't bring any useful information
// if hide transaction is enabled, so don't display it
int lowestGroup = m_group.count() - 1;
if (!m_config.isHideTransactions() || i != lowestGroup) {
row_odd = true;
result.append(QString::fromLatin1("<tr class=\"sectionheader\">"
"<td class=\"left%1\" "
"colspan=\"%2\">%3</td></tr>\n").arg(QString::number(i),
QString::number(columns.count()),
curGrpName));
csv.append(QString::fromLatin1("\"%1\"\n").arg(curGrpName));
}
if (i == lowestGroup) // lowest group has been switched...
isLowestGroupTotal = true; // ...so expect lowest group total
prevGrpNames.replace(i, curGrpName);
}
}
bool need_label = true;
QString tlink; // link information to account and transaction
if (!m_config.isHideTransactions() || rowRank == 4 || rowRank == 5) { // if hide transaction is enabled display only total rows i.e. rank = 4 || rank = 5
if (rowRank == 0 || rowRank == 3) {
// skip the opening and closing balance row,
// if the balance column is not shown
// rank = 0 for opening balance, rank = 3 for closing balance
if (!columns.contains(ctBalance))
continue;
result.append(QString::fromLatin1("<tr class=\"item%1\">").arg((*it_row).value(ctID)));
// ***DV***
} else if (rowRank == 1) {
row_odd = ! row_odd;
tlink = QString::fromLatin1("id=%1&tid=%2").arg((*it_row).value(ctAccountID), (*it_row).value(ctID));
result.append(QString::fromLatin1("<tr class=\"row-%1\">").arg(row_odd ? QLatin1String("odd") : QLatin1String("even")));
} else if (rowRank == 2) {
result.append(QString::fromLatin1("<tr class=\"item%1\">").arg(row_odd ? QLatin1Char('1') : QLatin1Char('0')));
} else if (rowRank == 4 || rowRank == 5) {
QList<TableRow>::const_iterator nextRow = std::next(it_row);
if ((m_config.rowType() == MyMoneyReport::eTag)) { //If we order by Tags don't show the Grand total as we can have multiple tags per transaction
continue;
} else if (rowRank == 4) {
if (nextRow != m_rows.end()) {
if (isLowestGroupTotal && m_config.isHideTransactions()) {
result.append(QLatin1String("<tr class=\"sectionfootermiddle\">"));
isLowestGroupTotal = false;
} else if ((*nextRow).value(ctRank) == QLatin1String("5")) {
result.append(QLatin1String("<tr class=\"sectionfooterfirst\">"));
} else {
result.append(QLatin1String("<tr class=\"sectionfooter\">"));
}
} else {
result.append(QLatin1String("<tr class=\"sectionfooter\">"));
}
} else if (rowRank == 5) {
if (nextRow != m_rows.end()) {
if ((*nextRow).value(ctRank) == QLatin1String("5"))
result.append(QLatin1String("<tr class=\"sectionfootermiddle\">"));
else
result.append(QLatin1String("<tr class=\"sectionfooterlast\">"));
}
} else {
result.append(QLatin1String("<tr class=\"sectionfooter\">"));
}
} else {
result.append(QString::fromLatin1("<tr class=\"row-%1\">").arg(row_odd ? QLatin1String("odd") : QLatin1String("even")));
}
} else {
continue;
}
//
// Columns
//
QList<cellTypeE>::ConstIterator it_column = columns.constBegin();
while (it_column != columns.constEnd()) {
QString data = (*it_row).value(*it_column);
// ***DV***
if (rowRank == 2) {
if (*it_column == ctValue)
data = (*it_row).value(ctSplit);
else if (*it_column == ctPostDate
|| *it_column == ctNumber
|| *it_column == ctPayee
|| *it_column == ctTag
|| *it_column == ctAction
|| *it_column == ctShares
|| *it_column == ctPrice
|| *it_column == ctNextDueDate
|| *it_column == ctBalance
|| *it_column == ctAccount
|| *it_column == ctName)
data.clear();
}
// ***DV***
else if (rowRank == 0 || rowRank == 3) {
if (*it_column == ctBalance) {
data = (*it_row).value(ctBalance);
if ((*it_row).value(ctID) == QLatin1String("A")) { // opening balance?
startingBalance = MyMoneyMoney(data);
balanceChange = MyMoneyMoney();
}
}
if (need_label) {
if ((*it_column == ctPayee) ||
(*it_column == ctCategory) ||
(*it_column == ctMemo)) {
if (!(*it_row).value(ctShares).isEmpty()) {
data = ((*it_row).value(ctID) == QLatin1String("A"))
? i18n("Initial Market Value")
: i18n("Ending Market Value");
} else {
data = ((*it_row).value(ctID) == QLatin1String("A"))
? i18n("Opening Balance")
: i18n("Closing Balance");
}
need_label = false;
}
}
}
// The 'balance' column is calculated at render-time
// but not printed on split lines
else if (*it_column == ctBalance && rowRank == 1) {
// Take the balance off the deepest group iterator
balanceChange += MyMoneyMoney((*it_row).value(ctValue, QLatin1String("0")));
data = (balanceChange + startingBalance).toString();
} else if ((rowRank == 4 || rowRank == 5)) {
// display total title but only if first column doesn't contain any data
if (it_column == columns.constBegin() && data.isEmpty()) {
result.append(QString::fromLatin1("<td class=\"left%1\">").arg((*it_row).value(ctDepth)));
if (rowRank == 4) {
if (!(*it_row).value(ctDepth).isEmpty())
result += i18nc("Total balance", "Total") + QLatin1Char(' ') + prevGrpNames.at((*it_row).value(ctDepth).toInt());
else
result += i18n("Grand Total");
}
result.append(QLatin1String("</td>"));
++it_column;
continue;
} else if (!m_subtotal.contains(*it_column)) { // don't display e.g. account in totals row
result.append(QLatin1String("<td></td>"));
++it_column;
continue;
}
}
// Figure out how to render the value in this column, depending on
// what its properties are.
//
// TODO: This and the i18n headings are handled
// as a set of parallel vectors. Would be much better to make a single
// vector of a properties class.
QString tlinkBegin, tlinkEnd;
if (!tlink.isEmpty()) {
tlinkBegin = QString::fromLatin1("<a href=ledger?%1>").arg(tlink);
tlinkEnd = QLatin1String("</a>");
}
QString currencyID = (*it_row).value(ctCurrency);
if (currencyID.isEmpty())
currencyID = file->baseCurrency().id();
int fraction = file->currency(currencyID).smallestAccountFraction();
if (m_config.isConvertCurrency()) // don't show currency id, if there is only single currency
currencyID.clear();
switch (cellGroup(*it_column)) {
case cgMoney:
if (data.isEmpty()) {
result.append(QString::fromLatin1("<td%1></td>")
.arg((*it_column == ctValue) ? QLatin1String(" class=\"value\"") : QString()));
csv.append(QLatin1String("\"\","));
} else if (MyMoneyMoney(data) == MyMoneyMoney::autoCalc) {
result.append(QString::fromLatin1("<td%1>%3%2%4</td>")
.arg((*it_column == ctValue) ? QLatin1String(" class=\"value\"") : QString(),
i18n("Calculated"), tlinkBegin, tlinkEnd));
csv.append(QString::fromLatin1("\"%1\",").arg(i18n("Calculated")));
} else {
auto value = MyMoneyMoney(data);
auto valueStr = value.formatMoney(fraction);
csv.append(QString::fromLatin1("\"%1 %2\",")
.arg(currencyID, valueStr));
QString colorBegin;
QString colorEnd;
if ((rowRank == 4 || rowRank == 5) && value.isNegative()) {
colorBegin = QString::fromLatin1("<font color=%1>").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name());
colorEnd = QLatin1String("</font>");
}
result.append(QString::fromLatin1("<td%1>%4%6%2&nbsp;%3%7%5</td>")
.arg((*it_column == ctValue) ? QLatin1String(" class=\"value\"") : QString(),
currencyID,
valueStr,
tlinkBegin, tlinkEnd,
colorBegin, colorEnd));
}
break;
case cgPercent:
if (data.isEmpty()) {
result.append(QLatin1String("<td></td>"));
csv.append(QLatin1String("\"\","));
} else {
auto value = MyMoneyMoney(data) * MyMoneyMoney(100, 1);
auto valueStr = value.formatMoney(fraction);
csv.append(QString::fromLatin1("%1%,").arg(valueStr));
QString colorBegin;
QString colorEnd;
if ((rowRank == 4 || rowRank == 5) && value.isNegative()) {
colorBegin = QString::fromLatin1("<font color=%1>").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name());
colorEnd = QLatin1String("</font>");
}
if ((rowRank == 4 || rowRank == 5) && value.isNegative())
valueStr = QString::fromLatin1("<font color=%1>%2</font>")
.arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name(), valueStr);
result.append(QString::fromLatin1("<td>%2%4%1%%5%3</td>").arg(valueStr, tlinkBegin, tlinkEnd, colorBegin, colorEnd));
}
break;
case cgPrice:
{
int pricePrecision = file->security(file->account((*it_row).value(ctAccountID)).currencyId()).pricePrecision();
result.append(QString::fromLatin1("<td>%3%2&nbsp;%1%4</td>")
.arg(MyMoneyMoney(data).formatMoney(QString(), pricePrecision),
currencyID, tlinkBegin, tlinkEnd));
csv.append(QString::fromLatin1("\"%1 %2\",").arg(currencyID,
MyMoneyMoney(data).formatMoney(QString(), pricePrecision, false)));
}
break;
case cgShares:
if (data.isEmpty()) {
result.append(QLatin1String("<td></td>"));
csv.append(QLatin1String("\"\","));
} else {
int sharesPrecision = MyMoneyMoney::denomToPrec(file->security(file->account((*it_row).value(ctAccountID)).currencyId()).smallestAccountFraction());
result += QString::fromLatin1("<td>%2%1%3</td>").arg(MyMoneyMoney(data).formatMoney(QString(), sharesPrecision),
tlinkBegin, tlinkEnd);
csv.append(QString::fromLatin1("\"%1\",").arg(MyMoneyMoney(data).formatMoney(QString(), sharesPrecision, false)));
}
break;
case cgDate:
// do this before we possibly change data
csv.append(QString::fromLatin1("\"%1\",").arg(data));
// if we have a locale() then use its date formatter
if (!data.isEmpty()) {
QDate qd = QDate::fromString(data, Qt::ISODate);
data = QLocale().toString(qd, QLocale::ShortFormat);
}
result.append(QString::fromLatin1("<td class=\"left%4\">%2%1%3</td>").arg(data, tlinkBegin, tlinkEnd, QString::number(prevGrpNames.count() - 1)));
break;
default:
result.append(QString::fromLatin1("<td class=\"left%4\">%2%1%3</td>").arg(data, tlinkBegin, tlinkEnd, QString::number(prevGrpNames.count() - 1)));
csv.append(QString::fromLatin1("\"%1\",").arg(data));
break;
}
++it_column;
tlink.clear();
}
result.append(QLatin1String("</tr>\n"));
csv.chop(1); // remove final comma
csv.append(QLatin1Char('\n'));
}
result.append(QLatin1String("</table>\n"));
}
QString ListTable::renderHTML() const
{
QString html, csv;
render(html, csv);
return html;
}
QString ListTable::renderCSV() const
{
QString html, csv;
render(html, csv);
return csv;
}
void ListTable::dump(const QString& file, const QString& context) const
{
QFile g(file);
g.open(QIODevice::WriteOnly | QIODevice::Text);
if (! context.isEmpty())
QTextStream(&g) << context.arg(renderHTML());
else
QTextStream(&g) << renderHTML();
g.close();
}
void ListTable::includeInvestmentSubAccounts()
{
// if we're not in expert mode, we need to make sure
// that all stock accounts for the selected investment
// account are also selected.
// In case we get called for a non investment only report we quit
if (KMyMoneyGlobalSettings::expertMode() || !m_config.isInvestmentsOnly()) {
return;
}
// get all investment subAccountsList but do not include those with zero balance
// or those which had no transactions during the timeframe of the report
QStringList accountIdList;
QStringList subAccountsList;
MyMoneyFile* file = MyMoneyFile::instance();
// get the report account filter
if (!m_config.accounts(accountIdList)
&& m_config.isInvestmentsOnly()) {
// this will only execute if this is an investment-only report
QList<MyMoneyAccount> accountList;
file->accountList(accountList);
QList<MyMoneyAccount>::const_iterator it_ma;
for (it_ma = accountList.constBegin(); it_ma != accountList.constEnd(); ++it_ma) {
- if ((*it_ma).accountType() == MyMoneyAccount::Investment) {
+ if ((*it_ma).accountType() == eMyMoney::Account::Investment) {
accountIdList.append((*it_ma).id());
}
}
}
QStringList::const_iterator it_a;
for (it_a = accountIdList.constBegin(); it_a != accountIdList.constEnd(); ++it_a) {
MyMoneyAccount acc = file->account(*it_a);
- if (acc.accountType() == MyMoneyAccount::Investment) {
+ if (acc.accountType() == eMyMoney::Account::Investment) {
QStringList::const_iterator it_b;
for (it_b = acc.accountList().constBegin(); it_b != acc.accountList().constEnd(); ++it_b) {
if (!accountIdList.contains(*it_b)) {
subAccountsList.append(*it_b);
}
}
}
}
if (m_config.isInvestmentsOnly()
&& !m_config.isIncludingUnusedAccounts()) {
// if the balance is not zero at the end, include the subaccount
QStringList::iterator it_balance;
for (it_balance = subAccountsList.begin(); it_balance != subAccountsList.end();) {
if (!file->balance((*it_balance), m_config.toDate()).isZero()) {
m_config.addAccount((*it_balance));
it_balance = subAccountsList.erase((it_balance));
} else {
++it_balance;
}
}
// if there are transactions for that subaccount, include them
MyMoneyTransactionFilter filter;
filter.setDateFilter(m_config.fromDate(), m_config.toDate());
filter.addAccount(subAccountsList);
filter.setReportAllSplits(false);
QList<MyMoneyTransaction> transactions = file->transactionList(filter);
QList<MyMoneyTransaction>::const_iterator it_t = transactions.constBegin();
//Check each split for a matching account
for (; it_t != transactions.constEnd(); ++it_t) {
const QList<MyMoneySplit>& splits = (*it_t).splits();
QList<MyMoneySplit>::const_iterator it_s = splits.begin();
for (; it_s != splits.end(); ++it_s) {
const QString& accountId = (*it_s).accountId();
if (!(*it_s).shares().isZero()
&& subAccountsList.contains(accountId)) {
subAccountsList.removeOne(accountId);
m_config.addAccount(accountId);
}
}
}
} else {
// if not an investment-only report or explicitly including unused accounts
// add all investment subaccounts
m_config.addAccount(subAccountsList);
}
}
ListTable::cellGroupE ListTable::cellGroup(const cellTypeE cellType)
{
switch (cellType) {
// the list of columns which represent money, so we can display them correctly
case ctValue:
case ctNetInvValue:
case ctMarketValue:
case ctBuys:
case ctSells:
case ctBuysST:
case ctSellsST:
case ctBuysLT:
case ctSellsLT:
case ctCapitalGain:
case ctCapitalGainST:
case ctCapitalGainLT:
case ctCashIncome:
case ctReinvestIncome:
case ctFees:
case ctInterest:
case ctStartingBalance:
case ctEndingBalance:
case ctBalance:
case ctCurrentBalance:
case ctBalanceWarning:
case ctMaxBalanceLimit:
case ctCreditWarning:
case ctMaxCreditLimit:
case ctLoanAmount:
case ctPeriodicPayment:
case ctFinalPayment:
case ctPayment:
return cgMoney;
case ctPrice:
case ctLastPrice:
case ctBuyPrice:
return cgPrice;
/* the list of columns which represent shares, which is like money except the
transaction currency will not be displayed*/
case ctShares:
return cgShares;
// the list of columns which represent a percentage, so we can display them correctly
case ctReturn:
case ctReturnInvestment:
case ctInterestRate:
case ctPercentageGain:
return cgPercent;
// the list of columns which represent dates, so we can display them correctly
case ctPostDate:
case ctEntryDate:
case ctNextDueDate:
case ctOpeningDate:
case ctNextInterestChange:
return cgDate;
default:
break;
}
return cgMisc;
}
QString ListTable::tableHeader(const cellTypeE cellType)
{
switch (cellType) {
case ctPostDate:
return i18n("Date");
case ctValue:
return i18n("Amount");
case ctNumber:
return i18n("Num");
case ctPayee:
return i18n("Payee");
case ctTag:
return i18n("Tags");
case ctCategory:
return i18n("Category");
case ctAccount:
return i18n("Account");
case ctMemo:
return i18n("Memo");
case ctTopCategory:
return i18n("Top Category");
case ctCategoryType:
return i18n("Category Type");
case ctMonth:
return i18n("Month");
case ctWeek:
return i18n("Week");
case ctReconcileFlag:
return i18n("Reconciled");
case ctAction:
return i18n("Action");
case ctShares:
return i18n("Shares");
case ctPrice:
return i18n("Price");
case ctLastPrice:
return i18n("Last Price");
case ctBuyPrice:
return i18n("Buy Price");
case ctNetInvValue:
return i18n("Net Value");
case ctBuys:
return i18n("Buy Value");
case ctSells:
return i18n("Sell Value");
case ctBuysST:
return i18n("Short-term Buy Value");
case ctSellsST:
return i18n("Short-term Sell Value");
case ctBuysLT:
return i18n("Long-term Buy Value");
case ctSellsLT:
return i18n("Long-term Sell Value");
case ctReinvestIncome:
return i18n("Dividends Reinvested");
case ctCashIncome:
return i18n("Dividends Paid Out");
case ctStartingBalance:
return i18n("Starting Balance");
case ctEndingBalance:
return i18n("Ending Balance");
case ctMarketValue:
return i18n("Market Value");
case ctReturn:
return i18n("Annualized Return");
case ctReturnInvestment:
return i18n("Return On Investment");
case ctFees:
return i18n("Fees");
case ctInterest:
return i18n("Interest");
case ctPayment:
return i18n("Payment");
case ctBalance:
return i18n("Balance");
case ctType:
return i18n("Type");
case ctName:
return i18nc("Account name", "Name");
case ctNextDueDate:
return i18n("Next Due Date");
case ctOccurence:
return i18n("Occurrence"); // krazy:exclude=spelling
case ctPaymentType:
return i18n("Payment Method");
case ctInstitution:
return i18n("Institution");
case ctDescription:
return i18n("Description");
case ctOpeningDate:
return i18n("Opening Date");
case ctCurrencyName:
return i18n("Currency");
case ctBalanceWarning:
return i18n("Balance Early Warning");
case ctMaxBalanceLimit:
return i18n("Balance Max Limit");
case ctCreditWarning:
return i18n("Credit Early Warning");
case ctMaxCreditLimit:
return i18n("Credit Max Limit");
case ctTax:
return i18n("Tax");
case ctFavorite:
return i18n("Preferred");
case ctLoanAmount:
return i18n("Loan Amount");
case ctInterestRate:
return i18n("Interest Rate");
case ctNextInterestChange:
return i18n("Next Interest Change");
case ctPeriodicPayment:
return i18n("Periodic Payment");
case ctFinalPayment:
return i18n("Final Payment");
case ctCurrentBalance:
return i18n("Current Balance");
case ctCapitalGain:
return i18n("Capital Gain");
case ctPercentageGain:
return i18n("Percentage Gain");
case ctCapitalGainST:
return i18n("Short-term Gain");
case ctCapitalGainLT:
return i18n("Long-term Gain");
default:
break;
}
return QLatin1String("None");
}
}
diff --git a/kmymoney/reports/listtable.h b/kmymoney/reports/listtable.h
index 8415387d4..e3c61c3ae 100644
--- a/kmymoney/reports/listtable.h
+++ b/kmymoney/reports/listtable.h
@@ -1,151 +1,153 @@
/***************************************************************************
listtable.h
-------------------
begin : Sat 28 jun 2008
copyright : (C) 2004-2005 by Ace Jones <acejones@users.sourceforge.net>
2008 by Alvaro Soliverez <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 LISTTABLE_H
#define LISTTABLE_H
// ----------------------------------------------------------------------------
// QT Includes
+#include <QVector>
+
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyreport.h"
#include "reporttable.h"
namespace reports
{
class ReportAccount;
/**
* Calculates a query of information about the transaction database.
*
* This is a middle-layer class, between the implementing classes and the engine. The
* MyMoneyReport class holds only the CONFIGURATION parameters. This
* class has some common methods used by querytable and objectinfo classes
*
* @author Alvaro Soliverez
*
* @short
**/
class ListTable : public ReportTable
{
public:
ListTable(const MyMoneyReport&);
QString renderHTML() const;
QString renderCSV() const;
void drawChart(KReportChartView&) const {}
void dump(const QString& file, const QString& context = QString()) const;
void init();
public:
enum cellTypeE /*{*/ /*Money*/
{ctValue, ctNetInvValue, ctMarketValue,
ctPrice, ctLastPrice, ctBuyPrice,
ctBuys, ctSells, ctBuysST, ctSellsST, ctBuysLT, ctSellsLT,
ctCapitalGain, ctCapitalGainST,ctCapitalGainLT,
ctCashIncome, ctReinvestIncome,
ctFees, ctInterest,
ctStartingBalance, ctEndingBalance, ctBalance, ctCurrentBalance,
ctBalanceWarning, ctMaxBalanceLimit, ctOpeningBalance,
ctCreditWarning, ctMaxCreditLimit,
ctLoanAmount, ctPeriodicPayment, ctFinalPayment, ctPayment,
/*Shares*/
ctShares,
/*Percent*/
ctReturn, ctReturnInvestment, ctInterestRate, ctPercentageGain,
/*Date*/
ctPostDate, ctEntryDate, ctNextDueDate, ctOpeningDate, ctNextInterestChange,
ctMonth, ctWeek, ctReconcileDate,
/*Misc*/
ctCurrency, ctCurrencyName, ctCommodity, ctID,
ctRank, ctSplit, ctMemo,
ctAccount, ctAccountID, ctTopAccount, ctInvestAccount, ctInstitution,
ctCategory, ctTopCategory, ctCategoryType,
ctNumber, ctReconcileFlag,
ctAction, ctTag, ctPayee, ctEquityType, ctType, ctName,
ctDepth, ctRowsCount, ctTax, ctFavorite, ctDescription, ctOccurence, ctPaymentType
};
/**
* Contains a single row in the table.
*
* Each column is a key/value pair, both strings. This class is just
* a QMap with the added ability to specify which columns you'd like to
* use as a sort key when you qHeapSort a list of these TableRows
*/
class TableRow: public QMap<cellTypeE, QString>
{
public:
bool operator< (const TableRow&) const;
bool operator<= (const TableRow&) const;
bool operator> (const TableRow&) const;
bool operator== (const TableRow&) const;
static void setSortCriteria(const QVector<cellTypeE>& _criteria) {
m_sortCriteria = _criteria;
}
private:
static QVector<cellTypeE> m_sortCriteria;
};
const QList<TableRow>& rows() {
return m_rows;
}
protected:
void render(QString&, QString&) const;
/**
* If not in expert mode, include all subaccounts for each selected
* investment account.
* For investment-only reports, it will also exclude the subaccounts
* that have a zero balance
*/
void includeInvestmentSubAccounts();
QList<TableRow> m_rows;
QList<cellTypeE> m_group;
/**
* Comma-separated list of columns to place BEFORE the subtotal column
*/
QList<cellTypeE> m_columns;
/**
* Name of the subtotal column
*/
QList<cellTypeE> m_subtotal;
/**
* Comma-separated list of columns to place AFTER the subtotal column
*/
QList<cellTypeE> m_postcolumns;
private:
enum cellGroupE { cgMoney, cgShares, cgPercent, cgDate, cgPrice, cgMisc };
static cellGroupE cellGroup(const cellTypeE cellType);
static QString tableHeader(const cellTypeE cellType);
};
}
#endif
diff --git a/kmymoney/reports/objectinfotable.cpp b/kmymoney/reports/objectinfotable.cpp
index 9cb0066b2..86f9b714e 100644
--- a/kmymoney/reports/objectinfotable.cpp
+++ b/kmymoney/reports/objectinfotable.cpp
@@ -1,363 +1,369 @@
/***************************************************************************
objectinfotable.cpp
-------------------
begin : Sat 28 jun 2008
copyright : (C) 2004-2005 by Ace Jones <acejones@users.sourceforge.net>
2008 by Alvaro Soliverez <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 "objectinfotable.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneyaccount.h"
+#include "mymoneyinstitution.h"
+#include "mymoneyprice.h"
+#include "mymoneypayee.h"
+#include "mymoneysplit.h"
+#include "mymoneytransaction.h"
#include "mymoneyreport.h"
#include "mymoneyexception.h"
#include "kmymoneyutils.h"
#include "reportaccount.h"
namespace reports
{
// ****************************************************************************
//
// ObjectInfoTable implementation
//
// ****************************************************************************
/**
* TODO
*
* - Collapse 2- & 3- groups when they are identical
* - Way more test cases (especially splits & transfers)
* - Option to collapse splits
* - Option to exclude transfers
*
*/
ObjectInfoTable::ObjectInfoTable(const MyMoneyReport& _report): ListTable(_report)
{
// separated into its own method to allow debugging (setting breakpoints
// directly in ctors somehow does not work for me (ipwizard))
// TODO: remove the init() method and move the code back to the ctor
init();
}
void ObjectInfoTable::init()
{
m_columns.clear();
m_group.clear();
m_subtotal.clear();
switch (m_config.rowType()) {
case MyMoneyReport::eSchedule:
constructScheduleTable();
m_columns << ctNextDueDate << ctName;
break;
case MyMoneyReport::eAccountInfo:
constructAccountTable();
m_columns << ctInstitution << ctType << ctName;
break;
case MyMoneyReport::eAccountLoanInfo:
constructAccountLoanTable();
m_columns << ctInstitution << ctType << ctName;
break;
default:
break;
}
// Sort the data to match the report definition
m_subtotal << ctValue;
switch (m_config.rowType()) {
case MyMoneyReport::eSchedule:
m_group << ctType;
m_subtotal << ctValue;
break;
case MyMoneyReport::eAccountInfo:
case MyMoneyReport::eAccountLoanInfo:
m_group << ctTopCategory << ctInstitution;
m_subtotal << ctCurrentBalance;
break;
default:
throw MYMONEYEXCEPTION("ObjectInfoTable::ObjectInfoTable(): unhandled row type");
}
QVector<cellTypeE> sort = QVector<cellTypeE>::fromList(m_group) << QVector<cellTypeE>::fromList(m_columns) << ctID << ctRank;
switch (m_config.rowType()) {
case MyMoneyReport::eSchedule:
if (m_config.detailLevel() == MyMoneyReport::eDetailAll) {
m_columns << ctName << ctPayee << ctPaymentType << ctOccurence
<< ctNextDueDate << ctCategory; // krazy:exclude=spelling
} else {
m_columns << ctName << ctPayee << ctPaymentType << ctOccurence
<< ctNextDueDate; // krazy:exclude=spelling
}
break;
case MyMoneyReport::eAccountInfo:
m_columns << ctType << ctName << ctNumber << ctDescription
<< ctOpeningDate << ctCurrencyName << ctBalanceWarning
<< ctCreditWarning << ctMaxCreditLimit
<< ctTax << ctFavorite;
break;
case MyMoneyReport::eAccountLoanInfo:
m_columns << ctType << ctName << ctNumber << ctDescription
<< ctOpeningDate << ctCurrencyName << ctPayee
<< ctLoanAmount << ctInterestRate << ctNextInterestChange
<< ctPeriodicPayment << ctFinalPayment << ctFavorite;
break;
default:
m_columns.clear();
}
TableRow::setSortCriteria(sort);
qSort(m_rows);
}
void ObjectInfoTable::constructScheduleTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneySchedule> schedules;
- schedules = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, m_config.fromDate(), m_config.toDate());
+ schedules = file->scheduleList(QString(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, m_config.fromDate(), m_config.toDate(), false);
QList<MyMoneySchedule>::const_iterator it_schedule = schedules.constBegin();
while (it_schedule != schedules.constEnd()) {
MyMoneySchedule schedule = *it_schedule;
ReportAccount account = schedule.account();
if (m_config.includes(account)) {
//get fraction for account
int fraction = account.fraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = MyMoneyFile::instance()->baseCurrency().smallestAccountFraction();
TableRow scheduleRow;
//convert to base currency if needed
MyMoneyMoney xr = MyMoneyMoney::ONE;
if (m_config.isConvertCurrency() && account.isForeignCurrency()) {
xr = account.baseCurrencyPrice(QDate::currentDate()).reduce();
}
// help for sort and render functions
scheduleRow[ctRank] = QLatin1Char('0');
//schedule data
scheduleRow[ctID] = schedule.id();
scheduleRow[ctName] = schedule.name();
scheduleRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate);
scheduleRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type());
scheduleRow[ctOccurence] = i18nc("Frequency of schedule", schedule.occurrenceToString().toLatin1()); // krazy:exclude=spelling
scheduleRow[ctPaymentType] = KMyMoneyUtils::paymentMethodToString(schedule.paymentType());
//scheduleRow["category"] = account.name();
//to get the payee we must look into the splits of the transaction
MyMoneyTransaction transaction = schedule.transaction();
MyMoneySplit split = transaction.splitByAccount(account.id(), true);
scheduleRow[ctValue] = (split.value() * xr).toString();
MyMoneyPayee payee = file->payee(split.payeeId());
scheduleRow[ctPayee] = payee.name();
m_rows += scheduleRow;
//the text matches the main split
bool transaction_text = m_config.match(&split);
if (m_config.detailLevel() == MyMoneyReport::eDetailAll) {
//get the information for all splits
QList<MyMoneySplit> splits = transaction.splits();
QList<MyMoneySplit>::const_iterator split_it = splits.constBegin();
for (; split_it != splits.constEnd(); ++split_it) {
TableRow splitRow;
ReportAccount splitAcc = (*split_it).accountId();
splitRow[ctRank] = QLatin1Char('1');
splitRow[ctID] = schedule.id();
splitRow[ctName] = schedule.name();
splitRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type());
splitRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate);
if ((*split_it).value() == MyMoneyMoney::autoCalc) {
splitRow[ctSplit] = MyMoneyMoney::autoCalc.toString();
} else if (! splitAcc.isIncomeExpense()) {
splitRow[ctSplit] = (*split_it).value().toString();
} else {
splitRow[ctSplit] = (- (*split_it).value()).toString();
}
//if it is an assett account, mark it as a transfer
if (! splitAcc.isIncomeExpense()) {
splitRow[ctCategory] = ((* split_it).value().isNegative())
? i18n("Transfer from %1" , splitAcc.fullName())
: i18n("Transfer to %1" , splitAcc.fullName());
} else {
splitRow [ctCategory] = splitAcc.fullName();
}
//add the split only if it matches the text or it matches the main split
if (m_config.match(&(*split_it))
|| transaction_text)
m_rows += splitRow;
}
}
}
++it_schedule;
}
}
void ObjectInfoTable::constructAccountTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
//make sure we have all subaccounts of investment accounts
includeInvestmentSubAccounts();
QList<MyMoneyAccount> accounts;
file->accountList(accounts);
QList<MyMoneyAccount>::const_iterator it_account = accounts.constBegin();
while (it_account != accounts.constEnd()) {
TableRow accountRow;
ReportAccount account = *it_account;
if (m_config.includes(account)
- && account.accountType() != MyMoneyAccount::Stock
+ && account.accountType() != eMyMoney::Account::Stock
&& !account.isClosed()) {
MyMoneyMoney value;
accountRow[ctRank] = QLatin1Char('0');
accountRow[ctTopCategory] = KMyMoneyUtils::accountTypeToString(account.accountGroup());
accountRow[ctInstitution] = (file->institution(account.institutionId())).name();
accountRow[ctType] = KMyMoneyUtils::accountTypeToString(account.accountType());
accountRow[ctName] = account.name();
accountRow[ctNumber] = account.number();
accountRow[ctDescription] = account.description();
accountRow[ctOpeningDate] = account.openingDate().toString(Qt::ISODate);
//accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol();
accountRow[ctCurrencyName] = (file->currency(account.currencyId())).name();
accountRow[ctBalanceWarning] = account.value("minBalanceEarly");
accountRow[ctMaxBalanceLimit] = account.value("minBalanceAbsolute");
accountRow[ctCreditWarning] = account.value("maxCreditEarly");
accountRow[ctMaxCreditLimit] = account.value("maxCreditAbsolute");
accountRow[ctTax] = account.value("Tax") == QLatin1String("Yes") ? i18nc("Is this a tax account?", "Yes") : QString();
accountRow[ctOpeningBalance] = account.value("OpeningBalanceAccount") == QLatin1String("Yes") ? i18nc("Is this an opening balance account?", "Yes") : QString();
accountRow[ctFavorite] = account.value("PreferredAccount") == QLatin1String("Yes") ? i18nc("Is this a favorite account?", "Yes") : QString();
//investment accounts show the balances of all its subaccounts
- if (account.accountType() == MyMoneyAccount::Investment) {
+ if (account.accountType() == eMyMoney::Account::Investment) {
value = investmentBalance(account);
} else {
value = file->balance(account.id());
}
//convert to base currency if needed
if (m_config.isConvertCurrency() && account.isForeignCurrency()) {
MyMoneyMoney xr = account.baseCurrencyPrice(QDate::currentDate()).reduce();
value = value * xr;
}
accountRow[ctCurrentBalance] = value.toString();
m_rows += accountRow;
}
++it_account;
}
}
void ObjectInfoTable::constructAccountLoanTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyAccount> accounts;
file->accountList(accounts);
QList<MyMoneyAccount>::const_iterator it_account = accounts.constBegin();
while (it_account != accounts.constEnd()) {
TableRow accountRow;
ReportAccount account = *it_account;
MyMoneyAccountLoan loan = *it_account;
if (m_config.includes(account) && account.isLoan() && !account.isClosed()) {
//convert to base currency if needed
MyMoneyMoney xr = MyMoneyMoney::ONE;
if (m_config.isConvertCurrency() && account.isForeignCurrency()) {
xr = account.baseCurrencyPrice(QDate::currentDate()).reduce();
}
accountRow[ctRank] = QLatin1Char('0');
accountRow[ctTopCategory] = KMyMoneyUtils::accountTypeToString(account.accountGroup());
accountRow[ctInstitution] = (file->institution(account.institutionId())).name();
accountRow[ctType] = KMyMoneyUtils::accountTypeToString(account.accountType());
accountRow[ctName] = account.name();
accountRow[ctNumber] = account.number();
accountRow[ctDescription] = account.description();
accountRow[ctOpeningDate] = account.openingDate().toString(Qt::ISODate);
//accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol();
accountRow[ctCurrencyName] = (file->currency(account.currencyId())).name();
accountRow[ctPayee] = file->payee(loan.payee()).name();
accountRow[ctLoanAmount] = (loan.loanAmount() * xr).toString();
accountRow[ctInterestRate] = (loan.interestRate(QDate::currentDate()) / MyMoneyMoney(100, 1) * xr).toString();
accountRow[ctNextInterestChange] = loan.nextInterestChange().toString(Qt::ISODate);
accountRow[ctPeriodicPayment] = (loan.periodicPayment() * xr).toString();
accountRow[ctFinalPayment] = (loan.finalPayment() * xr).toString();
accountRow[ctFavorite] = account.value("PreferredAccount") == QLatin1String("Yes") ? i18nc("Is this a favorite account?", "Yes") : QString();
MyMoneyMoney value = file->balance(account.id());
value = value * xr;
accountRow[ctCurrentBalance] = value.toString();
m_rows += accountRow;
}
++it_account;
}
}
MyMoneyMoney ObjectInfoTable::investmentBalance(const MyMoneyAccount& acc)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyMoney value = file->balance(acc.id());
QStringList accList = acc.accountList();
QStringList::const_iterator it_a = accList.constBegin();
for (; it_a != acc.accountList().constEnd(); ++it_a) {
MyMoneyAccount stock = file->account(*it_a);
try {
MyMoneyMoney val;
MyMoneyMoney balance = file->balance(stock.id());
MyMoneySecurity security = file->security(stock.currencyId());
const MyMoneyPrice &price = file->price(stock.currencyId(), security.tradingCurrency());
val = balance * price.rate(security.tradingCurrency());
// adjust value of security to the currency of the account
MyMoneySecurity accountCurrency = file->currency(acc.currencyId());
val = val * file->price(security.tradingCurrency(), accountCurrency.id()).rate(accountCurrency.id());
val = val.convert(acc.fraction());
value += val;
} catch (const MyMoneyException &e) {
qWarning("%s", qPrintable(QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e.what())));
}
}
return value;
}
}
diff --git a/kmymoney/reports/pivottable.cpp b/kmymoney/reports/pivottable.cpp
index 1c7799c54..f6841a196 100644
--- a/kmymoney/reports/pivottable.cpp
+++ b/kmymoney/reports/pivottable.cpp
@@ -1,2338 +1,2338 @@
/***************************************************************************
pivottable.cpp
-------------------
begin : Mon May 17 2004
copyright : (C) 2004-2005 by Ace Jones
email : <ace.j@hotpop.com>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Alvaro Soliverez <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 "pivottable.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QRegExp>
#include <QFile>
#include <QTextStream>
#include <QList>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "pivotgrid.h"
#include "reportdebug.h"
#include "kreportchartview.h"
#include "kmymoneyglobalsettings.h"
#include "kmymoneyutils.h"
#include "mymoneyforecast.h"
#include "mymoneyprice.h"
#include "mymoneyfile.h"
#include "mymoneybudget.h"
namespace KChart { class Widget; }
namespace reports
{
using KChart::Widget;
QString Debug::m_sTabs;
bool Debug::m_sEnabled = DEBUG_ENABLED_BY_DEFAULT;
QString Debug::m_sEnableKey;
Debug::Debug(const QString& _name): m_methodName(_name), m_enabled(m_sEnabled)
{
if (!m_enabled && _name == m_sEnableKey)
m_enabled = true;
if (m_enabled) {
qDebug("%s%s(): ENTER", qPrintable(m_sTabs), qPrintable(m_methodName));
m_sTabs.append("--");
}
}
Debug::~Debug()
{
if (m_enabled) {
m_sTabs.remove(0, 2);
qDebug("%s%s(): EXIT", qPrintable(m_sTabs), qPrintable(m_methodName));
if (m_methodName == m_sEnableKey)
m_enabled = false;
}
}
void Debug::output(const QString& _text)
{
if (m_enabled)
qDebug("%s%s(): %s", qPrintable(m_sTabs), qPrintable(m_methodName), qPrintable(_text));
}
PivotTable::PivotTable(const MyMoneyReport& _report):
ReportTable(_report),
m_runningSumsCalculated(false)
{
init();
}
void PivotTable::init()
{
DEBUG_ENTER(Q_FUNC_INFO);
//
// Initialize locals
//
MyMoneyFile* file = MyMoneyFile::instance();
//
// Initialize member variables
//
//make sure we have all subaccounts of investment accounts
includeInvestmentSubAccounts();
m_config.validDateRange(m_beginDate, m_endDate);
// If we need to calculate running sums, it does not make sense
// to show a row total column
if (m_config.isRunningSum())
m_config.setShowingRowTotals(false);
if (m_config.isRunningSum() &&
!m_config.isIncludingPrice() &&
!m_config.isIncludingAveragePrice() &&
!m_config.isIncludingMovingAverage())
m_startColumn = 1;
else
m_startColumn = 0;
m_numColumns = columnValue(m_endDate) - columnValue(m_beginDate) + 1 + m_startColumn; // 1 for m_beginDate values and m_startColumn for opening balance values
//Load what types of row the report is going to show
loadRowTypeList();
//
// Initialize outer groups of the grid
//
if (m_config.rowType() == MyMoneyReport::eAssetLiability) {
- m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Asset), PivotOuterGroup(m_numColumns));
- m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Liability), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder, true /* inverted */));
+ m_grid.insert(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Asset), PivotOuterGroup(m_numColumns));
+ m_grid.insert(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Liability), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder, true /* inverted */));
} else {
- m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Income), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder - 2));
- m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Expense), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder - 1, true /* inverted */));
+ m_grid.insert(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Income), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder - 2));
+ m_grid.insert(KMyMoneyUtils::accountTypeToString(eMyMoney::Account::Expense), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder - 1, true /* inverted */));
//
// Create rows for income/expense reports with all accounts included
//
if (m_config.isIncludingUnusedAccounts())
createAccountRows();
}
//
// Initialize grid totals
//
m_grid.m_total = PivotGridRowSet(m_numColumns);
//
// Get opening balances
// Only net worth report qualifies
if (m_startColumn == 1)
calculateOpeningBalances();
//
// Calculate budget mapping
// (for budget reports only)
//
if (m_config.hasBudget())
calculateBudgetMapping();
// prices report doesn't need transactions, but it needs account stub
// otherwise fillBasePriceUnit won't do nothing
if (m_config.isIncludingPrice() ||
m_config.isIncludingAveragePrice()) {
QList<MyMoneyAccount> accounts;
file->accountList(accounts);
foreach (const auto acc, accounts) {
if (acc.isInvest()) {
const ReportAccount repAcc = acc;
if (m_config.includes(repAcc)) {
const auto outergroup = acc.accountTypeToString(acc.accountType());
assignCell(outergroup, repAcc, 0, MyMoneyMoney(), false, false); // add account stub
}
}
}
} else {
//
// Populate all transactions into the row/column pivot grid
//
QList<MyMoneyTransaction> transactions;
m_config.setReportAllSplits(false);
m_config.setConsiderCategory(true);
try {
transactions = file->transactionList(m_config);
} catch (const MyMoneyException &e) {
qDebug("ERR: %s thrown in %s(%ld)", qPrintable(e.what()), qPrintable(e.file()), e.line());
throw e;
}
DEBUG_OUTPUT(QString("Found %1 matching transactions").arg(transactions.count()));
// Include scheduled transactions if required
if (m_config.isIncludingSchedules()) {
// Create a custom version of the report filter, excluding date
// We'll use this to compare the transaction against
MyMoneyTransactionFilter schedulefilter(m_config);
schedulefilter.setDateFilter(QDate(), QDate());
// Get the real dates from the config filter
QDate configbegin, configend;
m_config.validDateRange(configbegin, configend);
QList<MyMoneySchedule> schedules = file->scheduleList();
QList<MyMoneySchedule>::const_iterator it_schedule = schedules.constBegin();
while (it_schedule != schedules.constEnd()) {
// If the transaction meets the filter
MyMoneyTransaction tx = (*it_schedule).transaction();
if (!(*it_schedule).isFinished() && schedulefilter.match(tx)) {
// Keep the id of the schedule with the transaction so that
// we can do the autocalc later on in case of a loan payment
tx.setValue("kmm-schedule-id", (*it_schedule).id());
// Get the dates when a payment will be made within the report window
QDate nextpayment = (*it_schedule).adjustedNextPayment(configbegin);
if (nextpayment.isValid()) {
// Add one transaction for each date
QList<QDate> paymentDates = (*it_schedule).paymentDates(nextpayment, configend);
QList<QDate>::const_iterator it_date = paymentDates.constBegin();
while (it_date != paymentDates.constEnd()) {
//if the payment occurs in the past, enter it tomorrow
if (QDate::currentDate() >= *it_date) {
tx.setPostDate(QDate::currentDate().addDays(1));
} else {
tx.setPostDate(*it_date);
}
if (tx.postDate() <= configend
&& tx.postDate() >= configbegin) {
transactions += tx;
}
DEBUG_OUTPUT(QString("Added transaction for schedule %1 on %2").arg((*it_schedule).id()).arg((*it_date).toString()));
++it_date;
}
}
}
++it_schedule;
}
}
// whether asset & liability transactions are actually to be considered
// transfers
bool al_transfers = (m_config.rowType() == MyMoneyReport::eExpenseIncome) && (m_config.isIncludingTransfers());
//this is to store balance for loan accounts when not included in the report
QMap<QString, MyMoneyMoney> loanBalances;
QList<MyMoneyTransaction>::const_iterator it_transaction = transactions.constBegin();
int colofs = columnValue(m_beginDate) - m_startColumn;
while (it_transaction != transactions.constEnd()) {
MyMoneyTransaction tx = (*it_transaction);
QDate postdate = tx.postDate();
if (postdate < m_beginDate) {
qDebug("MyMoneyFile::transactionList returned a transaction that is outside the date filter, skipping it");
++it_transaction;
continue;
}
int column = columnValue(postdate) - colofs;
// check if we need to call the autocalculation routine
if (tx.isLoanPayment() && tx.hasAutoCalcSplit() && (tx.value("kmm-schedule-id").length() > 0)) {
// make sure to consider any autocalculation for loan payments
MyMoneySchedule sched = file->schedule(tx.value("kmm-schedule-id"));
const MyMoneySplit& split = tx.amortizationSplit();
if (!split.id().isEmpty()) {
ReportAccount splitAccount = file->account(split.accountId());
- MyMoneyAccount::accountTypeE type = splitAccount.accountGroup();
+ eMyMoney::Account type = splitAccount.accountGroup();
QString outergroup = KMyMoneyUtils::accountTypeToString(type);
//if the account is included in the report, calculate the balance from the cells
if (m_config.includes(splitAccount)) {
loanBalances[splitAccount.id()] = cellBalance(outergroup, splitAccount, column, false);
} else {
//if it is not in the report and also not in loanBalances, get the balance from the file
if (!loanBalances.contains(splitAccount.id())) {
QDate dueDate = sched.nextDueDate();
//if the payment is overdue, use current date
if (dueDate < QDate::currentDate())
dueDate = QDate::currentDate();
//get the balance from the file for the date
loanBalances[splitAccount.id()] = file->balance(splitAccount.id(), dueDate.addDays(-1));
}
}
KMyMoneyUtils::calculateAutoLoan(sched, tx, loanBalances);
//if the loan split is not included in the report, update the balance for the next occurrence
if (!m_config.includes(splitAccount)) {
QList<MyMoneySplit>::ConstIterator it_loanSplits;
for (it_loanSplits = tx.splits().constBegin(); it_loanSplits != tx.splits().constEnd(); ++it_loanSplits) {
if ((*it_loanSplits).isAmortizationSplit() && (*it_loanSplits).accountId() == splitAccount.id())
loanBalances[splitAccount.id()] = loanBalances[splitAccount.id()] + (*it_loanSplits).shares();
}
}
}
}
QList<MyMoneySplit> splits = tx.splits();
QList<MyMoneySplit>::const_iterator it_split = splits.constBegin();
while (it_split != splits.constEnd()) {
ReportAccount splitAccount = (*it_split).accountId();
// Each split must be further filtered, because if even one split matches,
// the ENTIRE transaction is returned with all splits (even non-matching ones)
if (m_config.includes(splitAccount) && m_config.match(&(*it_split))) {
// reverse sign to match common notation for cash flow direction, only for expense/income splits
MyMoneyMoney reverse(splitAccount.isIncomeExpense() ? -1 : 1, 1);
MyMoneyMoney value;
// the outer group is the account class (major account type)
- MyMoneyAccount::accountTypeE type = splitAccount.accountGroup();
+ eMyMoney::Account type = splitAccount.accountGroup();
QString outergroup = KMyMoneyUtils::accountTypeToString(type);
value = (*it_split).shares();
bool stockSplit = tx.isStockSplit();
if (!stockSplit) {
// retrieve the value in the account's underlying currency
if (value != MyMoneyMoney::autoCalc) {
value = value * reverse;
} else {
qDebug("PivotTable::PivotTable(): This must not happen");
value = MyMoneyMoney(); // keep it 0 so far
}
// Except in the case of transfers on an income/expense report
- if (al_transfers && (type == MyMoneyAccount::Asset || type == MyMoneyAccount::Liability)) {
+ if (al_transfers && (type == eMyMoney::Account::Asset || type == eMyMoney::Account::Liability)) {
outergroup = i18n("Transfers");
value = -value;
}
}
// add the value to its correct position in the pivot table
assignCell(outergroup, splitAccount, column, value, false, stockSplit);
}
++it_split;
}
++it_transaction;
}
}
//
// Get forecast data
//
if (m_config.isIncludingForecast())
calculateForecast();
//
//Insert Price data
//
if (m_config.isIncludingPrice())
fillBasePriceUnit(ePrice);
//
//Insert Average Price data
//
if (m_config.isIncludingAveragePrice()) {
fillBasePriceUnit(eActual);
calculateMovingAverage();
}
//
// Collapse columns to match column type
//
if (m_config.columnPitch() > 1)
collapseColumns();
//
// Calculate the running sums
// (for running sum reports only)
//
if (m_config.isRunningSum())
calculateRunningSums();
//
// Calculate Moving Average
//
if (m_config.isIncludingMovingAverage())
calculateMovingAverage();
//
// Calculate Budget Difference
//
if (m_config.isIncludingBudgetActuals())
calculateBudgetDiff();
//
// Convert all values to the deep currency
//
convertToDeepCurrency();
//
// Convert all values to the base currency
//
if (m_config.isConvertCurrency())
convertToBaseCurrency();
//
// Determine column headings
//
calculateColumnHeadings();
//
// Calculate row and column totals
//
calculateTotals();
//
// If using mixed time, calculate column for current date
//
m_config.setCurrentDateColumn(currentDateColumn());
}
void PivotTable::collapseColumns()
{
DEBUG_ENTER(Q_FUNC_INFO);
int columnpitch = m_config.columnPitch();
if (columnpitch != 1) {
int sourcemonth = (m_config.isColumnsAreDays())
// use the user's locale to determine the week's start
? (m_beginDate.dayOfWeek() + 8 - QLocale().firstDayOfWeek()) % 7
: m_beginDate.month();
int sourcecolumn = m_startColumn;
int destcolumn = m_startColumn;
while (sourcecolumn < m_numColumns) {
if (sourcecolumn != destcolumn) {
#if 0
// TODO: Clean up this rather inefficient kludge. We really should jump by an entire
// destcolumn at a time on RS reports, and calculate the proper sourcecolumn to use,
// allowing us to clear and accumulate only ONCE per destcolumn
if (m_config_f.isRunningSum())
clearColumn(destcolumn);
#endif
accumulateColumn(destcolumn, sourcecolumn);
}
if (++sourcecolumn < m_numColumns) {
if ((sourcemonth++ % columnpitch) == 0) {
if (sourcecolumn != ++destcolumn)
clearColumn(destcolumn);
}
}
}
m_numColumns = destcolumn + 1;
}
}
void PivotTable::accumulateColumn(int destcolumn, int sourcecolumn)
{
DEBUG_ENTER(Q_FUNC_INFO);
DEBUG_OUTPUT(QString("From Column %1 to %2").arg(sourcecolumn).arg(destcolumn));
// iterate over outer groups
PivotGrid::iterator it_outergroup = m_grid.begin();
while (it_outergroup != m_grid.end()) {
// iterate over inner groups
PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
// iterator over rows
PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
if ((*it_row)[eActual].count() <= sourcecolumn)
throw MYMONEYEXCEPTION(QString("Sourcecolumn %1 out of grid range (%2) in PivotTable::accumulateColumn").arg(sourcecolumn).arg((*it_row)[eActual].count()));
if ((*it_row)[eActual].count() <= destcolumn)
throw MYMONEYEXCEPTION(QString("Destcolumn %1 out of grid range (%2) in PivotTable::accumulateColumn").arg(sourcecolumn).arg((*it_row)[eActual].count()));
(*it_row)[eActual][destcolumn] += (*it_row)[eActual][sourcecolumn];
++it_row;
}
++it_innergroup;
}
++it_outergroup;
}
}
void PivotTable::clearColumn(int column)
{
DEBUG_ENTER(Q_FUNC_INFO);
DEBUG_OUTPUT(QString("Column %1").arg(column));
// iterate over outer groups
PivotGrid::iterator it_outergroup = m_grid.begin();
while (it_outergroup != m_grid.end()) {
// iterate over inner groups
PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
// iterator over rows
PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
if ((*it_row)[eActual].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::accumulateColumn").arg(column).arg((*it_row)[eActual].count()));
(*it_row++)[eActual][column] = PivotCell();
}
++it_innergroup;
}
++it_outergroup;
}
}
void PivotTable::calculateColumnHeadings()
{
DEBUG_ENTER(Q_FUNC_INFO);
// one column for the opening balance
if (m_startColumn == 1)
m_columnHeadings.append("Opening");
int columnpitch = m_config.columnPitch();
if (columnpitch == 0) {
// output the warning but don't crash by dividing with 0
qWarning("PivotTable::calculateColumnHeadings() Invalid column pitch");
return;
}
// if this is a days-based report
if (m_config.isColumnsAreDays()) {
if (columnpitch == 1) {
QDate columnDate = m_beginDate;
int column = m_startColumn;
while (column++ < m_numColumns) {
QString heading = QLocale().monthName(columnDate.month(), QLocale::ShortFormat) + ' ' + QString::number(columnDate.day());
columnDate = columnDate.addDays(1);
m_columnHeadings.append(heading);
}
} else {
QDate day = m_beginDate;
QDate prv = m_beginDate;
// use the user's locale to determine the week's start
int dow = (day.dayOfWeek() + 8 - QLocale().firstDayOfWeek()) % 7;
while (day <= m_endDate) {
if (((dow % columnpitch) == 0) || (day == m_endDate)) {
m_columnHeadings.append(QString("%1&nbsp;%2 - %3&nbsp;%4")
.arg(QLocale().monthName(prv.month(), QLocale::ShortFormat))
.arg(prv.day())
.arg(QLocale().monthName(day.month(), QLocale::ShortFormat))
.arg(day.day()));
prv = day.addDays(1);
}
day = day.addDays(1);
dow++;
}
}
}
// else it's a months-based report
else {
if (columnpitch == 12) {
int year = m_beginDate.year();
int column = m_startColumn;
while (column++ < m_numColumns)
m_columnHeadings.append(QString::number(year++));
} else {
int year = m_beginDate.year();
bool includeyear = (m_beginDate.year() != m_endDate.year());
int segment = (m_beginDate.month() - 1) / columnpitch;
int column = m_startColumn;
while (column++ < m_numColumns) {
QString heading = QLocale().monthName(1 + segment * columnpitch, QLocale::ShortFormat);
if (columnpitch != 1)
heading += '-' + QLocale().monthName((1 + segment) * columnpitch, QLocale::ShortFormat);
if (includeyear)
heading += ' ' + QString::number(year);
m_columnHeadings.append(heading);
if (++segment >= 12 / columnpitch) {
segment -= 12 / columnpitch;
++year;
}
}
}
}
}
void PivotTable::createAccountRows()
{
DEBUG_ENTER(Q_FUNC_INFO);
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyAccount> accounts;
file->accountList(accounts);
QList<MyMoneyAccount>::const_iterator it_account = accounts.constBegin();
while (it_account != accounts.constEnd()) {
ReportAccount account = *it_account;
// only include this item if its account group is included in this report
// and if the report includes this account
if (m_config.includes(*it_account)) {
DEBUG_OUTPUT(QString("Includes account %1").arg(account.name()));
// the row group is the account class (major account type)
QString outergroup = KMyMoneyUtils::accountTypeToString(account.accountGroup());
// place into the 'opening' column...
assignCell(outergroup, account, 0, MyMoneyMoney());
}
++it_account;
}
}
void PivotTable::calculateOpeningBalances()
{
DEBUG_ENTER(Q_FUNC_INFO);
// First, determine the inclusive dates of the report. Normally, that's just
// the begin & end dates of m_config_f. However, if either of those dates are
// blank, we need to use m_beginDate and/or m_endDate instead.
QDate from = m_config.fromDate();
QDate to = m_config.toDate();
if (! from.isValid())
from = m_beginDate;
if (! to.isValid())
to = m_endDate;
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyAccount> accounts;
file->accountList(accounts);
QList<MyMoneyAccount>::const_iterator it_account = accounts.constBegin();
while (it_account != accounts.constEnd()) {
ReportAccount account = *it_account;
// only include this item if its account group is included in this report
// and if the report includes this account
if (m_config.includes(*it_account)) {
//do not include account if it is closed and it has no transactions in the report period
if (account.isClosed()) {
//check if the account has transactions for the report timeframe
MyMoneyTransactionFilter filter;
filter.addAccount(account.id());
filter.setDateFilter(m_beginDate, m_endDate);
filter.setReportAllSplits(false);
QList<MyMoneyTransaction> transactions = file->transactionList(filter);
//if a closed account has no transactions in that timeframe, do not include it
if (transactions.size() == 0) {
DEBUG_OUTPUT(QString("DOES NOT INCLUDE account %1").arg(account.name()));
++it_account;
continue;
}
}
DEBUG_OUTPUT(QString("Includes account %1").arg(account.name()));
// the row group is the account class (major account type)
QString outergroup = KMyMoneyUtils::accountTypeToString(account.accountGroup());
// extract the balance of the account for the given begin date, which is
// the opening balance plus the sum of all transactions prior to the begin
// date
// this is in the underlying currency
MyMoneyMoney value = file->balance(account.id(), from.addDays(-1));
// place into the 'opening' column...
assignCell(outergroup, account, 0, value);
} else {
DEBUG_OUTPUT(QString("DOES NOT INCLUDE account %1").arg(account.name()));
}
++it_account;
}
}
void PivotTable::calculateRunningSums(PivotInnerGroup::iterator& it_row)
{
MyMoneyMoney runningsum = it_row.value()[eActual][0].calculateRunningSum(MyMoneyMoney());
int column = m_startColumn;
while (column < m_numColumns) {
if (it_row.value()[eActual].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateRunningSums").arg(column).arg(it_row.value()[eActual].count()));
runningsum = it_row.value()[eActual][column].calculateRunningSum(runningsum);
++column;
}
}
void PivotTable::calculateRunningSums()
{
DEBUG_ENTER(Q_FUNC_INFO);
m_runningSumsCalculated = true;
PivotGrid::iterator it_outergroup = m_grid.begin();
while (it_outergroup != m_grid.end()) {
PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
#if 0
MyMoneyMoney runningsum = it_row.value()[0];
int column = m_startColumn;
while (column < m_numColumns) {
if (it_row.value()[eActual].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateRunningSums").arg(column).arg(it_row.value()[eActual].count()));
runningsum = (it_row.value()[eActual][column] += runningsum);
++column;
}
#endif
calculateRunningSums(it_row);
++it_row;
}
++it_innergroup;
}
++it_outergroup;
}
}
MyMoneyMoney PivotTable::cellBalance(const QString& outergroup, const ReportAccount& _row, int _column, bool budget)
{
if (m_runningSumsCalculated) {
qDebug("You must not call PivotTable::cellBalance() after calling PivotTable::calculateRunningSums()");
throw MYMONEYEXCEPTION(QString("You must not call PivotTable::cellBalance() after calling PivotTable::calculateRunningSums()"));
}
// for budget reports, if this is the actual value, map it to the account which
// holds its budget
ReportAccount row = _row;
if (!budget && m_config.hasBudget()) {
QString newrow = m_budgetMap[row.id()];
// if there was no mapping found, then the budget report is not interested
// in this account.
if (newrow.isEmpty())
return MyMoneyMoney();
row = newrow;
}
// ensure the row already exists (and its parental hierarchy)
createRow(outergroup, row, true);
// Determine the inner group from the top-most parent account
QString innergroup(row.topParentName());
if (m_numColumns <= _column)
throw MYMONEYEXCEPTION(QString("Column %1 out of m_numColumns range (%2) in PivotTable::cellBalance").arg(_column).arg(m_numColumns));
if (m_grid[outergroup][innergroup][row][eActual].count() <= _column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::cellBalance").arg(_column).arg(m_grid[outergroup][innergroup][row][eActual].count()));
MyMoneyMoney balance;
if (budget)
balance = m_grid[outergroup][innergroup][row][eBudget][0].cellBalance(MyMoneyMoney());
else
balance = m_grid[outergroup][innergroup][row][eActual][0].cellBalance(MyMoneyMoney());
int column = m_startColumn;
while (column < _column) {
if (m_grid[outergroup][innergroup][row][eActual].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::cellBalance").arg(column).arg(m_grid[outergroup][innergroup][row][eActual].count()));
balance = m_grid[outergroup][innergroup][row][eActual][column].cellBalance(balance);
++column;
}
return balance;
}
void PivotTable::calculateBudgetMapping()
{
DEBUG_ENTER(Q_FUNC_INFO);
MyMoneyFile* file = MyMoneyFile::instance();
// Only do this if there is at least one budget in the file
if (file->countBudgets()) {
// Select a budget
//
// It will choose the first budget in the list for the start year of the report if no budget is selected
MyMoneyBudget budget = MyMoneyBudget();
QList<MyMoneyBudget> budgets = file->budgetList();
bool validBudget = false;
//check that the selected budget is valid
if (m_config.budget() != "Any") {
QList<MyMoneyBudget>::const_iterator budgets_it = budgets.constBegin();
while (budgets_it != budgets.constEnd()) {
//pick the budget by id
if ((*budgets_it).id() == m_config.budget()) {
budget = file->budget((*budgets_it).id());
validBudget = true;
break;
}
++budgets_it;
}
}
//if no valid budget has been selected
if (!validBudget) {
//if the budget list is empty, just return
if (budgets.count() == 0) {
return;
}
QList<MyMoneyBudget>::const_iterator budgets_it = budgets.constBegin();
while (budgets_it != budgets.constEnd()) {
//pick the first budget that matches the report start year
if ((*budgets_it).budgetStart().year() == QDate::currentDate().year()) {
budget = file->budget((*budgets_it).id());
break;
}
++budgets_it;
}
//if it can't find a matching budget, take the first one on the list
if (budget.id().isEmpty()) {
budget = budgets[0];
}
//assign the budget to the report
m_config.setBudget(budget.id(), m_config.isIncludingBudgetActuals());
}
// Dump the budget
//qDebug() << "Budget " << budget.name() << ": ";
// Go through all accounts in the system to build the mapping
QList<MyMoneyAccount> accounts;
file->accountList(accounts);
QList<MyMoneyAccount>::const_iterator it_account = accounts.constBegin();
while (it_account != accounts.constEnd()) {
//include only the accounts selected for the report
if (m_config.includes(*it_account)) {
QString id = (*it_account).id();
QString acid = id;
// If the budget contains this account outright
if (budget.contains(id)) {
// Add it to the mapping
m_budgetMap[acid] = id;
// qDebug() << ReportAccount(acid).debugName() << " self-maps / type =" << budget.account(id).budgetLevel();
}
// Otherwise, search for a parent account which includes sub-accounts
else {
//if includeBudgetActuals, include all accounts regardless of whether in budget or not
if (m_config.isIncludingBudgetActuals()) {
m_budgetMap[acid] = id;
// qDebug() << ReportAccount(acid).debugName() << " maps to " << ReportAccount(id).debugName();
}
do {
id = file->account(id).parentAccountId();
if (budget.contains(id)) {
if (budget.account(id).budgetSubaccounts()) {
m_budgetMap[acid] = id;
// qDebug() << ReportAccount(acid).debugName() << " maps to " << ReportAccount(id).debugName();
break;
}
}
} while (! id.isEmpty());
}
}
++it_account;
} // end while looping through the accounts in the file
// Place the budget values into the budget grid
QList<MyMoneyBudget::AccountGroup> baccounts = budget.getaccounts();
QList<MyMoneyBudget::AccountGroup>::const_iterator it_bacc = baccounts.constBegin();
while (it_bacc != baccounts.constEnd()) {
ReportAccount splitAccount = (*it_bacc).id();
//include the budget account only if it is included in the report
if (m_config.includes(splitAccount)) {
- MyMoneyAccount::accountTypeE type = splitAccount.accountGroup();
+ eMyMoney::Account type = splitAccount.accountGroup();
QString outergroup = KMyMoneyUtils::accountTypeToString(type);
// reverse sign to match common notation for cash flow direction, only for expense/income splits
- MyMoneyMoney reverse((splitAccount.accountType() == MyMoneyAccount::Expense) ? -1 : 1, 1);
+ MyMoneyMoney reverse((splitAccount.accountType() == eMyMoney::Account::Expense) ? -1 : 1, 1);
const QMap<QDate, MyMoneyBudget::PeriodGroup>& periods = (*it_bacc).getPeriods();
// skip the account if it has no periods
if (periods.count() < 1) {
++it_bacc;
continue;
}
MyMoneyMoney value = (*periods.begin()).amount() * reverse;
int column = m_startColumn;
// based on the kind of budget it is, deal accordingly
switch ((*it_bacc).budgetLevel()) {
case MyMoneyBudget::AccountGroup::eYearly:
// divide the single yearly value by 12 and place it in each column
value /= MyMoneyMoney(12, 1);
// intentional fall through
case MyMoneyBudget::AccountGroup::eNone:
case MyMoneyBudget::AccountGroup::eMax:
case MyMoneyBudget::AccountGroup::eMonthly:
// place the single monthly value in each column of the report
// only add the value if columns are monthly or longer
if (m_config.columnType() == MyMoneyReport::eBiMonths
|| m_config.columnType() == MyMoneyReport::eMonths
|| m_config.columnType() == MyMoneyReport::eYears
|| m_config.columnType() == MyMoneyReport::eQuarters) {
QDate budgetDate = budget.budgetStart();
while (column < m_numColumns && budget.budgetStart().addYears(1) > budgetDate) {
//only show budget values if the budget year and the column date match
//no currency conversion is done here because that is done for all columns later
if (budgetDate > columnDate(column)) {
++column;
} else {
if (budgetDate >= m_beginDate.addDays(-m_beginDate.day() + 1)
&& budgetDate <= m_endDate.addDays(m_endDate.daysInMonth() - m_endDate.day())
&& budgetDate > (columnDate(column).addMonths(-m_config.columnType()))) {
assignCell(outergroup, splitAccount, column, value, true /*budget*/);
}
budgetDate = budgetDate.addMonths(1);
}
}
}
break;
case MyMoneyBudget::AccountGroup::eMonthByMonth:
// place each value in the appropriate column
// budget periods are supposed to come in order just like columns
{
QMap<QDate, MyMoneyBudget::PeriodGroup>::const_iterator it_period = periods.begin();
while (it_period != periods.end() && column < m_numColumns) {
if ((*it_period).startDate() > columnDate(column)) {
++column;
} else {
switch (m_config.columnType()) {
case MyMoneyReport::eYears:
case MyMoneyReport::eBiMonths:
case MyMoneyReport::eQuarters:
case MyMoneyReport::eMonths: {
if ((*it_period).startDate() >= m_beginDate.addDays(-m_beginDate.day() + 1)
&& (*it_period).startDate() <= m_endDate.addDays(m_endDate.daysInMonth() - m_endDate.day())
&& (*it_period).startDate() > (columnDate(column).addMonths(-m_config.columnType()))) {
//no currency conversion is done here because that is done for all columns later
value = (*it_period).amount() * reverse;
assignCell(outergroup, splitAccount, column, value, true /*budget*/);
}
++it_period;
break;
}
default:
break;
}
}
}
break;
}
}
}
++it_bacc;
}
} // end if there was a budget
}
void PivotTable::convertToBaseCurrency()
{
DEBUG_ENTER(Q_FUNC_INFO);
MyMoneyFile* file = MyMoneyFile::instance();
int fraction = file->baseCurrency().smallestAccountFraction();
QList<ERowType> rowTypeList = m_rowTypeList;
rowTypeList.removeOne(eAverage);
PivotGrid::iterator it_outergroup = m_grid.begin();
while (it_outergroup != m_grid.end()) {
PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
auto column = 0;
while (column < m_numColumns) {
if (it_row.value()[eActual].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::convertToBaseCurrency").arg(column).arg(it_row.value()[eActual].count()));
QDate valuedate = columnDate(column);
//get base price for that date
MyMoneyMoney conversionfactor = it_row.key().baseCurrencyPrice(valuedate, m_config.isSkippingZero());
int pricePrecision;
if (it_row.key().isInvest())
pricePrecision = file->security(it_row.key().currencyId()).pricePrecision();
else
pricePrecision = MyMoneyMoney::denomToPrec(fraction);
foreach (const auto rowType, rowTypeList) {
//calculate base value
MyMoneyMoney oldval = it_row.value()[rowType][column];
MyMoneyMoney value = (oldval * conversionfactor).reduce();
//convert to lowest fraction
if (rowType == ePrice)
it_row.value()[rowType][column] = PivotCell(value.convertPrecision(pricePrecision));
else
it_row.value()[rowType][column] = PivotCell(value.convert(fraction));
DEBUG_OUTPUT_IF(conversionfactor != MyMoneyMoney::ONE , QString("Factor of %1, value was %2, now %3").arg(conversionfactor).arg(DEBUG_SENSITIVE(oldval)).arg(DEBUG_SENSITIVE(it_row.value()[rowType][column].toDouble())));
}
++column;
}
++it_row;
}
++it_innergroup;
}
++it_outergroup;
}
}
void PivotTable::convertToDeepCurrency()
{
DEBUG_ENTER(Q_FUNC_INFO);
MyMoneyFile* file = MyMoneyFile::instance();
PivotGrid::iterator it_outergroup = m_grid.begin();
while (it_outergroup != m_grid.end()) {
PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
auto column = 0;
while (column < m_numColumns) {
if (it_row.value()[eActual].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::convertToDeepCurrency").arg(column).arg(it_row.value()[eActual].count()));
QDate valuedate = columnDate(column);
//get conversion factor for the account and date
MyMoneyMoney conversionfactor = it_row.key().deepCurrencyPrice(valuedate, m_config.isSkippingZero());
//use the fraction relevant to the account at hand
int fraction = it_row.key().currency().smallestAccountFraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = file->baseCurrency().smallestAccountFraction();
//convert to deep currency
MyMoneyMoney oldval = it_row.value()[eActual][column];
MyMoneyMoney value = (oldval * conversionfactor).reduce();
//reduce to lowest fraction
it_row.value()[eActual][column] = PivotCell(value.convert(fraction));
//convert price data
if (m_config.isIncludingPrice()) {
MyMoneyMoney oldPriceVal = it_row.value()[ePrice][column];
MyMoneyMoney priceValue = (oldPriceVal * conversionfactor).reduce();
it_row.value()[ePrice][column] = PivotCell(priceValue.convert(10000));
}
DEBUG_OUTPUT_IF(conversionfactor != MyMoneyMoney::ONE , QString("Factor of %1, value was %2, now %3").arg(conversionfactor).arg(DEBUG_SENSITIVE(oldval)).arg(DEBUG_SENSITIVE(it_row.value()[eActual][column].toDouble())));
++column;
}
++it_row;
}
++it_innergroup;
}
++it_outergroup;
}
}
void PivotTable::calculateTotals()
{
//insert the row type that is going to be used
for (int i = 0; i < m_rowTypeList.size(); ++i) {
for (int k = 0; k < m_numColumns; ++k) {
m_grid.m_total[ m_rowTypeList[i] ].append(PivotCell());
}
}
//
// Outer groups
//
// iterate over outer groups
PivotGrid::iterator it_outergroup = m_grid.begin();
while (it_outergroup != m_grid.end()) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
for (int k = 0; k < m_numColumns; ++k) {
(*it_outergroup).m_total[ m_rowTypeList[i] ].append(PivotCell());
}
}
//
// Inner Groups
//
PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
for (int k = 0; k < m_numColumns; ++k) {
(*it_innergroup).m_total[ m_rowTypeList[i] ].append(PivotCell());
}
}
//
// Rows
//
PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
//
// Columns
//
auto column = 0;
while (column < m_numColumns) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
if (it_row.value()[ m_rowTypeList[i] ].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, row columns").arg(column).arg(it_row.value()[ m_rowTypeList[i] ].count()));
if ((*it_innergroup).m_total[ m_rowTypeList[i] ].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, inner group totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count()));
//calculate total
MyMoneyMoney value = it_row.value()[ m_rowTypeList[i] ][column];
(*it_innergroup).m_total[ m_rowTypeList[i] ][column] += value;
(*it_row)[ m_rowTypeList[i] ].m_total += value;
}
++column;
}
++it_row;
}
//
// Inner Row Group Totals
//
auto column = 0;
while (column < m_numColumns) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
if ((*it_innergroup).m_total[ m_rowTypeList[i] ].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, inner group totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count()));
if ((*it_outergroup).m_total[ m_rowTypeList[i] ].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, outer group totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count()));
//calculate totals
MyMoneyMoney value = (*it_innergroup).m_total[ m_rowTypeList[i] ][column];
(*it_outergroup).m_total[ m_rowTypeList[i] ][column] += value;
(*it_innergroup).m_total[ m_rowTypeList[i] ].m_total += value;
}
++column;
}
++it_innergroup;
}
//
// Outer Row Group Totals
//
const bool isIncomeExpense = (m_config.rowType() == MyMoneyReport::eExpenseIncome);
const bool invert_total = (*it_outergroup).m_inverted;
auto column = 0;
while (column < m_numColumns) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
if (m_grid.m_total[ m_rowTypeList[i] ].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, grid totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count()));
//calculate actual totals
MyMoneyMoney value = (*it_outergroup).m_total[ m_rowTypeList[i] ][column];
(*it_outergroup).m_total[ m_rowTypeList[i] ].m_total += value;
//so far the invert only applies to actual and budget
if (invert_total && m_rowTypeList[i] != eBudgetDiff && m_rowTypeList[i] != eForecast)
value = -value;
// forecast income expense reports should be inverted as oposed to asset/liability reports
if (invert_total && isIncomeExpense && m_rowTypeList[i] == eForecast)
value = -value;
m_grid.m_total[ m_rowTypeList[i] ][column] += value;
}
++column;
}
++it_outergroup;
}
//
// Report Totals
//
auto totalcolumn = 0;
while (totalcolumn < m_numColumns) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
if (m_grid.m_total[ m_rowTypeList[i] ].count() <= totalcolumn)
throw MYMONEYEXCEPTION(QString("Total column %1 out of grid range (%2) in PivotTable::calculateTotals, grid totals").arg(totalcolumn).arg(m_grid.m_total[ m_rowTypeList[i] ].count()));
//calculate actual totals
MyMoneyMoney value = m_grid.m_total[ m_rowTypeList[i] ][totalcolumn];
m_grid.m_total[ m_rowTypeList[i] ].m_total += value;
}
++totalcolumn;
}
}
void PivotTable::assignCell(const QString& outergroup, const ReportAccount& _row, int column, MyMoneyMoney value, bool budget, bool stockSplit)
{
DEBUG_ENTER(Q_FUNC_INFO);
DEBUG_OUTPUT(QString("Parameters: %1,%2,%3,%4,%5").arg(outergroup).arg(_row.debugName()).arg(column).arg(DEBUG_SENSITIVE(value.toDouble())).arg(budget));
// for budget reports, if this is the actual value, map it to the account which
// holds its budget
ReportAccount row = _row;
if (!budget && m_config.hasBudget()) {
QString newrow = m_budgetMap[row.id()];
// if there was no mapping found, then the budget report is not interested
// in this account.
if (newrow.isEmpty())
return;
row = newrow;
}
// ensure the row already exists (and its parental hierarchy)
createRow(outergroup, row, true);
// Determine the inner group from the top-most parent account
QString innergroup(row.topParentName());
if (m_numColumns <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of m_numColumns range (%2) in PivotTable::assignCell").arg(column).arg(m_numColumns));
if (m_grid[outergroup][innergroup][row][eActual].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::assignCell").arg(column).arg(m_grid[outergroup][innergroup][row][eActual].count()));
if (m_grid[outergroup][innergroup][row][eBudget].count() <= column)
throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::assignCell").arg(column).arg(m_grid[outergroup][innergroup][row][eBudget].count()));
if (!stockSplit) {
// Determine whether the value should be inverted before being placed in the row
if (m_grid[outergroup].m_inverted)
value = -value;
// Add the value to the grid cell
if (budget) {
m_grid[outergroup][innergroup][row][eBudget][column] += value;
} else {
// If it is loading an actual value for a budget report
// check whether it is a subaccount of a budget account (include subaccounts)
// If so, check if is the same currency and convert otherwise
if (m_config.hasBudget() &&
row.id() != _row.id() &&
row.currencyId() != _row.currencyId()) {
ReportAccount origAcc = _row;
MyMoneyMoney rate = origAcc.foreignCurrencyPrice(row.currencyId(), columnDate(column), false);
m_grid[outergroup][innergroup][row][eActual][column] += (value * rate).reduce();
} else {
m_grid[outergroup][innergroup][row][eActual][column] += value;
}
}
} else {
m_grid[outergroup][innergroup][row][eActual][column] += PivotCell::stockSplit(value);
}
}
void PivotTable::createRow(const QString& outergroup, const ReportAccount& row, bool recursive)
{
DEBUG_ENTER(Q_FUNC_INFO);
// Determine the inner group from the top-most parent account
QString innergroup(row.topParentName());
if (! m_grid.contains(outergroup)) {
DEBUG_OUTPUT(QString("Adding group [%1]").arg(outergroup));
m_grid[outergroup] = PivotOuterGroup(m_numColumns);
}
if (! m_grid[outergroup].contains(innergroup)) {
DEBUG_OUTPUT(QString("Adding group [%1][%2]").arg(outergroup).arg(innergroup));
m_grid[outergroup][innergroup] = PivotInnerGroup(m_numColumns);
}
if (! m_grid[outergroup][innergroup].contains(row)) {
DEBUG_OUTPUT(QString("Adding row [%1][%2][%3]").arg(outergroup).arg(innergroup).arg(row.debugName()));
m_grid[outergroup][innergroup][row] = PivotGridRowSet(m_numColumns);
if (recursive && !row.isTopLevel())
createRow(outergroup, row.parent(), recursive);
}
}
int PivotTable::columnValue(const QDate& _date) const
{
if (m_config.isColumnsAreDays())
return (m_beginDate.daysTo(_date));
else
return (_date.year() * 12 + _date.month());
}
QDate PivotTable::columnDate(int column) const
{
if (m_config.isColumnsAreDays())
return m_beginDate.addDays(m_config.columnPitch() * column - m_startColumn);
else
return m_beginDate.addMonths(m_config.columnPitch() * column).addDays(-m_startColumn);
}
QString PivotTable::renderCSV() const
{
DEBUG_ENTER(Q_FUNC_INFO);
MyMoneyFile* file = MyMoneyFile::instance();
int pricePrecision = 0;
int currencyPrecision = 0;
int precision = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction());
bool isMultipleCurrencies = false;
//
// Table Header
//
QString result = i18n("Account");
auto column = 0;
while (column < m_numColumns) {
result += QString(",%1").arg(QString(m_columnHeadings[column++]));
if (m_rowTypeList.size() > 1) {
QString separator;
separator = separator.fill(',', m_rowTypeList.size() - 1);
result += separator;
}
}
//show total columns
if (m_config.isShowingRowTotals())
result += QString(",%1").arg(i18nc("Total balance", "Total"));
result += '\n';
// Row Type Header
if (m_rowTypeList.size() > 1) {
auto column = 0;
while (column < m_numColumns) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
result += QString(",%1").arg(m_columnTypeHeaderList[i]);
}
column++;
}
if (m_config.isShowingRowTotals()) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
result += QString(",%1").arg(m_columnTypeHeaderList[i]);
}
}
result += '\n';
}
//
// Outer groups
//
// iterate over outer groups
PivotGrid::const_iterator it_outergroup = m_grid.begin();
while (it_outergroup != m_grid.end()) {
//
// Outer Group Header
//
if (!(m_config.isIncludingPrice() || m_config.isIncludingAveragePrice()))
result += it_outergroup.key() + '\n';
//
// Inner Groups
//
PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin();
int rownum = 0;
while (it_innergroup != (*it_outergroup).end()) {
//
// Rows
//
QString innergroupdata;
PivotInnerGroup::const_iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
ReportAccount rowname = it_row.key();
//
// Columns
//
QString rowdata;
auto column = 0;
bool isUsed = false;
for (int i = 0; i < m_rowTypeList.size(); ++i)
isUsed |= it_row.value()[ m_rowTypeList[i] ][0].isUsed();
- if (it_row.key().accountType() != MyMoneyAccount::Investment) {
+ if (it_row.key().accountType() != eMyMoney::Account::Investment) {
while (column < m_numColumns) {
//show columns
foreach (const auto rowType, m_rowTypeList) {
if (rowType == ePrice) {
if (pricePrecision == 0) {
if (it_row.key().isInvest()) {
pricePrecision = file->currency(it_row.key().currencyId()).pricePrecision();
precision = pricePrecision;
} else
precision = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction());
} else
precision = pricePrecision;
} else {
if (currencyPrecision == 0) {
if (it_row.key().isInvest()) // stock account isn't eveluated in currency, so take investment account instead
currencyPrecision = MyMoneyMoney::denomToPrec(it_row.key().parent().fraction());
else
currencyPrecision = MyMoneyMoney::denomToPrec(it_row.key().fraction());
precision = currencyPrecision;
} else
precision = currencyPrecision;
}
rowdata += QString(",\"%1\"").arg(it_row.value()[rowType][column].formatMoney(QString(), precision, false));
isUsed |= it_row.value()[rowType][column].isUsed();
}
column++;
}
if (m_config.isShowingRowTotals()) {
for (int i = 0; i < m_rowTypeList.size(); ++i)
rowdata += QString(",\"%1\"").arg((*it_row)[ m_rowTypeList[i] ].m_total.formatMoney(QString(), precision, false));
}
} else {
for (auto i = 0; i < m_numColumns + m_rowTypeList.size(); ++i)
rowdata.append(',');;
}
//
// Row Header
//
if (!rowname.isClosed() || isUsed) {
innergroupdata += "\"" + QString().fill(' ', rowname.hierarchyDepth() - 1) + rowname.name();
// if we don't convert the currencies to the base currency and the
// current row contains a foreign currency, then we append the currency
// to the name of the account
if (!m_config.isConvertCurrency() && rowname.isForeignCurrency())
innergroupdata += QString(" (%1)").arg(rowname.currencyId());
innergroupdata += '\"';
if (isUsed)
innergroupdata += rowdata;
innergroupdata += '\n';
if (!isMultipleCurrencies && rowname.isForeignCurrency())
isMultipleCurrencies = true;
if (!m_containsNonBaseCurrency && rowname.isForeignCurrency())
m_containsNonBaseCurrency = true;
}
++it_row;
}
//
// Inner Row Group Totals
//
bool finishrow = true;
QString finalRow;
bool isUsed = false;
if (m_config.detailLevel() == MyMoneyReport::eDetailAll && ((*it_innergroup).size() > 1)) {
// Print the individual rows
result += innergroupdata;
if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) {
// Start the TOTALS row
finalRow = i18nc("Total balance", "Total");
isUsed = true;
} else {
++rownum;
finishrow = false;
}
} else {
// Start the single INDIVIDUAL ACCOUNT row
ReportAccount rowname = (*it_innergroup).begin().key();
isUsed |= !rowname.isClosed();
finalRow = "\"" + QString().fill(' ', rowname.hierarchyDepth() - 1) + rowname.name();
if (!m_config.isConvertCurrency() && rowname.isForeignCurrency())
finalRow += QString(" (%1)").arg(rowname.currencyId());
finalRow += "\"";
}
// Finish the row started above, unless told not to
if (finishrow) {
auto column = 0;
for (int i = 0; i < m_rowTypeList.size(); ++i)
isUsed |= (*it_innergroup).m_total[ m_rowTypeList[i] ][0].isUsed();
while (column < m_numColumns) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
isUsed |= (*it_innergroup).m_total[ m_rowTypeList[i] ][column].isUsed();
finalRow += QString(",\"%1\"").arg((*it_innergroup).m_total[ m_rowTypeList[i] ][column].formatMoney(QString(), precision, false));
}
column++;
}
if (m_config.isShowingRowTotals()) {
for (int i = 0; i < m_rowTypeList.size(); ++i)
finalRow += QString(",\"%1\"").arg((*it_innergroup).m_total[ m_rowTypeList[i] ].m_total.formatMoney(QString(), precision, false));
}
finalRow += '\n';
}
if (isUsed) {
result += finalRow;
++rownum;
}
++it_innergroup;
}
//
// Outer Row Group Totals
//
if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) {
result += QString("%1 %2").arg(i18nc("Total balance", "Total")).arg(it_outergroup.key());
auto column = 0;
while (column < m_numColumns) {
for (int i = 0; i < m_rowTypeList.size(); ++i)
result += QString(",\"%1\"").arg((*it_outergroup).m_total[ m_rowTypeList[i] ][column].formatMoney(QString(), precision, false));
column++;
}
if (m_config.isShowingRowTotals()) {
for (int i = 0; i < m_rowTypeList.size(); ++i)
result += QString(",\"%1\"").arg((*it_outergroup).m_total[ m_rowTypeList[i] ].m_total.formatMoney(QString(), precision, false));
}
result += '\n';
}
++it_outergroup;
}
//
// Report Totals
//
if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) {
result += i18n("Grand Total");
auto totalcolumn = 0;
while (totalcolumn < m_numColumns) {
for (int i = 0; i < m_rowTypeList.size(); ++i)
result += QString(",\"%1\"").arg(m_grid.m_total[ m_rowTypeList[i] ][totalcolumn].formatMoney(QString(), precision, false));
totalcolumn++;
}
if (m_config.isShowingRowTotals()) {
for (int i = 0; i < m_rowTypeList.size(); ++i)
result += QString(",\"%1\"").arg(m_grid.m_total[ m_rowTypeList[i] ].m_total.formatMoney(QString(), precision, false));
}
result += '\n';
}
return result;
}
QString PivotTable::renderHTML() const
{
DEBUG_ENTER(Q_FUNC_INFO);
MyMoneyFile* file = MyMoneyFile::instance();
int pricePrecision = 0;
int currencyPrecision = 0;
int precision = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction());
QString colspan = QString(" colspan=\"%1\"").arg(m_numColumns + 1 + (m_config.isShowingRowTotals() ? 1 : 0));
// setup a leftborder for better readability of budget vs actual reports
QString leftborder;
if (m_rowTypeList.size() > 1)
leftborder = " class=\"leftborder\"";
//
// Table Header
//
QString result = QString("\n\n<table class=\"report\" cellspacing=\"0\">\n"
"<thead><tr class=\"itemheader\">\n<th>%1</th>").arg(i18n("Account"));
QString headerspan;
int span = m_rowTypeList.size();
headerspan = QString(" colspan=\"%1\"").arg(span);
auto column = 0;
while (column < m_numColumns)
result += QString("<th%1>%2</th>").arg(headerspan, QString(m_columnHeadings[column++]).replace(QRegExp(" "), "<br>"));
if (m_config.isShowingRowTotals())
result += QString("<th%1>%2</th>").arg(headerspan).arg(i18nc("Total balance", "Total"));
result += "</tr></thead>\n";
//
// Header for multiple columns
//
if (span > 1) {
result += "<tr><td></td>";
auto column = 0;
while (column < m_numColumns) {
QString lb;
if (column != 0)
lb = leftborder;
for (int i = 0; i < m_rowTypeList.size(); ++i) {
result += QString("<td%2>%1</td>")
.arg(m_columnTypeHeaderList[i])
.arg(i == 0 ? lb : QString());
}
column++;
}
if (m_config.isShowingRowTotals()) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
result += QString("<td%2>%1</td>")
.arg(m_columnTypeHeaderList[i])
.arg(i == 0 ? leftborder : QString());
}
}
result += "</tr>";
}
// Skip the body of the report if the report only calls for totals to be shown
if (m_config.detailLevel() != MyMoneyReport::eDetailTotal) {
//
// Outer groups
//
// Need to sort the outergroups. They can't always be sorted by name. So we create a list of
// map iterators, and sort that. Then we'll iterate through the map iterators and use those as
// before.
//
// I hope this doesn't bog the performance of reports, given that we're copying the entire report
// data. If this is a perf hit, we could change to storing outergroup pointers, I think.
QList<PivotOuterGroup> outergroups;
PivotGrid::const_iterator it_outergroup_map = m_grid.begin();
while (it_outergroup_map != m_grid.end()) {
outergroups.push_back(it_outergroup_map.value());
// copy the name into the outergroup, because we will now lose any association with
// the map iterator
outergroups.back().m_displayName = it_outergroup_map.key();
++it_outergroup_map;
}
qSort(outergroups.begin(), outergroups.end());
QList<PivotOuterGroup>::const_iterator it_outergroup = outergroups.constBegin();
while (it_outergroup != outergroups.constEnd()) {
//
// Outer Group Header
//
if (!(m_config.isIncludingPrice() || m_config.isIncludingAveragePrice()))
result += QString("<tr class=\"sectionheader\"><td class=\"left\"%1>%2</td></tr>\n").arg(colspan).arg((*it_outergroup).m_displayName);
// Skip the inner groups if the report only calls for outer group totals to be shown
if (m_config.detailLevel() != MyMoneyReport::eDetailGroup) {
//
// Inner Groups
//
PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin();
int rownum = 0;
while (it_innergroup != (*it_outergroup).end()) {
//
// Rows
//
QString innergroupdata;
PivotInnerGroup::const_iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
//
// Columns
//
QString rowdata;
auto column = 0;
pricePrecision = 0; // new row => new account => new precision
currencyPrecision = 0;
bool isUsed = it_row.value()[eActual][0].isUsed();
- if (it_row.key().accountType() != MyMoneyAccount::Investment) {
+ if (it_row.key().accountType() != eMyMoney::Account::Investment) {
while (column < m_numColumns) {
QString lb;
if (column > 0)
lb = leftborder;
foreach (const auto rowType, m_rowTypeList) {
if (rowType == ePrice) {
if (pricePrecision == 0) {
if (it_row.key().isInvest()) {
pricePrecision = file->currency(it_row.key().currencyId()).pricePrecision();
precision = pricePrecision;
} else
precision = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction());
} else
precision = pricePrecision;
} else {
if (currencyPrecision == 0) {
if (it_row.key().isInvest()) // stock account isn't eveluated in currency, so take investment account instead
currencyPrecision = MyMoneyMoney::denomToPrec(it_row.key().parent().fraction());
else
currencyPrecision = MyMoneyMoney::denomToPrec(it_row.key().fraction());
precision = currencyPrecision;
} else
precision = currencyPrecision;
}
rowdata += QString("<td%2>%1</td>")
.arg(coloredAmount(it_row.value()[rowType][column], QString(), precision))
.arg(lb);
lb.clear();
isUsed |= it_row.value()[rowType][column].isUsed();
}
++column;
}
if (m_config.isShowingRowTotals()) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
rowdata += QString("<td%2>%1</td>")
.arg(coloredAmount(it_row.value()[ m_rowTypeList[i] ].m_total, QString(), precision))
.arg(i == 0 ? leftborder : QString());
}
}
} else
rowdata += QString(QLatin1Literal("<td colspan=%1></td>")).arg(m_numColumns + m_rowTypeList.size());
//
// Row Header
//
ReportAccount rowname = it_row.key();
// don't show closed accounts if they have not been used
if (!rowname.isClosed() || isUsed) {
innergroupdata += QString("<tr class=\"row-%1\"%2><td%3 class=\"left\" style=\"text-indent: %4.0em\">%5%6</td>")
.arg(rownum & 0x01 ? "even" : "odd")
.arg(rowname.isTopLevel() ? " id=\"topparent\"" : "")
.arg("") //.arg((*it_row).m_total.isZero() ? colspan : "") // colspan the distance if this row will be blank
.arg(rowname.hierarchyDepth() - 1)
.arg(rowname.name().replace(QRegExp(" "), "&nbsp;"))
.arg((m_config.isConvertCurrency() || !rowname.isForeignCurrency()) ? QString() : QString(" (%1)").arg(rowname.currency().id()));
// Don't print this row if it's going to be all zeros
// TODO: Uncomment this, and deal with the case where the data
// is zero, but the budget is non-zero
//if ( !(*it_row).m_total.isZero() )
innergroupdata += rowdata;
innergroupdata += "</tr>\n";
if (!m_containsNonBaseCurrency && rowname.isForeignCurrency())
m_containsNonBaseCurrency = true;
}
++it_row;
}
//
// Inner Row Group Totals
//
bool finishrow = true;
QString finalRow;
bool isUsed = false;
if (m_config.detailLevel() == MyMoneyReport::eDetailAll && ((*it_innergroup).size() > 1)) {
// Print the individual rows
result += innergroupdata;
if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) {
// Start the TOTALS row
finalRow = QString("<tr class=\"row-%1\" id=\"subtotal\"><td class=\"left\">&nbsp;&nbsp;%2</td>")
.arg(rownum & 0x01 ? "even" : "odd")
.arg(i18nc("Total balance", "Total"));
// don't suppress display of totals
isUsed = true;
} else {
finishrow = false;
++rownum;
}
} else {
// Start the single INDIVIDUAL ACCOUNT row
// FIXME: There is a bit of a bug here with class=leftX. There's only a finite number
// of classes I can define in the .CSS file, and the user can theoretically nest deeper.
// The right solution is to use style=Xem, and calculate X. Let's see if anyone complains
// first :) Also applies to the row header case above.
// FIXED: I found it in one of my reports and changed it to the proposed method.
// This works for me (ipwizard)
ReportAccount rowname = (*it_innergroup).begin().key();
isUsed |= !rowname.isClosed();
finalRow = QString("<tr class=\"row-%1\"%2><td class=\"left\" style=\"text-indent: %3.0em;\">%5%6</td>")
.arg(rownum & 0x01 ? "even" : "odd")
.arg(m_config.detailLevel() == MyMoneyReport::eDetailAll ? "id=\"solo\"" : "")
.arg(rowname.hierarchyDepth() - 1)
.arg(rowname.name().replace(QRegExp(" "), "&nbsp;"))
.arg((m_config.isConvertCurrency() || !rowname.isForeignCurrency()) ? QString() : QString(" (%1)").arg(rowname.currency().id()));
}
// Finish the row started above, unless told not to
if (finishrow) {
auto column = 0;
isUsed |= (*it_innergroup).m_total[eActual][0].isUsed();
while (column < m_numColumns) {
QString lb;
if (column != 0)
lb = leftborder;
for (int i = 0; i < m_rowTypeList.size(); ++i) {
finalRow += QString("<td%2>%1</td>")
.arg(coloredAmount((*it_innergroup).m_total[ m_rowTypeList[i] ][column], QString(), precision))
.arg(i == 0 ? lb : QString());
isUsed |= (*it_innergroup).m_total[ m_rowTypeList[i] ][column].isUsed();
}
column++;
}
if (m_config.isShowingRowTotals()) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
finalRow += QString("<td%2>%1</td>")
.arg(coloredAmount((*it_innergroup).m_total[ m_rowTypeList[i] ].m_total, QString(), precision))
.arg(i == 0 ? leftborder : QString());
}
}
finalRow += "</tr>\n";
if (isUsed) {
result += finalRow;
++rownum;
}
}
++it_innergroup;
} // end while iterating on the inner groups
} // end if detail level is not "group"
//
// Outer Row Group Totals
//
if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) {
result += QString("<tr class=\"sectionfooter\"><td class=\"left\">%1&nbsp;%2</td>").arg(i18nc("Total balance", "Total")).arg((*it_outergroup).m_displayName);
auto column = 0;
while (column < m_numColumns) {
QString lb;
if (column != 0)
lb = leftborder;
for (int i = 0; i < m_rowTypeList.size(); ++i) {
result += QString("<td%2>%1</td>")
.arg(coloredAmount((*it_outergroup).m_total[ m_rowTypeList[i] ][column], QString(), precision))
.arg(i == 0 ? lb : QString());
}
column++;
}
if (m_config.isShowingRowTotals()) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
result += QString("<td%2>%1</td>")
.arg(coloredAmount((*it_outergroup).m_total[ m_rowTypeList[i] ].m_total, QString(), precision))
.arg(i == 0 ? leftborder : QString());
}
}
result += "</tr>\n";
}
++it_outergroup;
} // end while iterating on the outergroups
} // end if detail level is not "total"
//
// Report Totals
//
if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) {
result += QString("<tr class=\"spacer\"><td>&nbsp;</td></tr>\n");
result += QString("<tr class=\"reportfooter\"><td class=\"left\">%1</td>").arg(i18n("Grand Total"));
auto totalcolumn = 0;
while (totalcolumn < m_numColumns) {
QString lb;
if (totalcolumn != 0)
lb = leftborder;
for (int i = 0; i < m_rowTypeList.size(); ++i) {
result += QString("<td%2>%1</td>")
.arg(coloredAmount(m_grid.m_total[ m_rowTypeList[i] ][totalcolumn], QString(), precision))
.arg(i == 0 ? lb : QString());
}
totalcolumn++;
}
if (m_config.isShowingRowTotals()) {
for (int i = 0; i < m_rowTypeList.size(); ++i) {
result += QString("<td%2>%1</td>")
.arg(coloredAmount(m_grid.m_total[ m_rowTypeList[i] ].m_total, QString(), precision))
.arg(i == 0 ? leftborder : QString());
}
}
result += "</tr>\n";
}
result += "</table>\n";
return result;
}
void PivotTable::dump(const QString& file, const QString& /* context */) const
{
QFile g(file);
g.open(QIODevice::WriteOnly);
QTextStream(&g) << renderHTML();
g.close();
}
void PivotTable::drawChart(KReportChartView& chartView) const
{
chartView.drawPivotChart(m_grid, m_config, m_numColumns, m_columnHeadings, m_rowTypeList, m_columnTypeHeaderList);
}
QString PivotTable::coloredAmount(const MyMoneyMoney& amount, const QString& currencySymbol, int prec) const
{
const auto value = amount.formatMoney(currencySymbol, prec);
if (amount.isNegative())
return QString::fromLatin1("<font color=%1>%2</font>")
.arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name(), value);
else
return value;
}
void PivotTable::calculateBudgetDiff()
{
PivotGrid::iterator it_outergroup = m_grid.begin();
while (it_outergroup != m_grid.end()) {
PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
int column = m_startColumn;
switch (it_row.key().accountGroup()) {
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Asset:
+ case eMyMoney::Account::Income:
+ case eMyMoney::Account::Asset:
while (column < m_numColumns) {
it_row.value()[eBudgetDiff][column] = it_row.value()[eActual][column] - it_row.value()[eBudget][column];
++column;
}
break;
- case MyMoneyAccount::Expense:
- case MyMoneyAccount::Liability:
+ case eMyMoney::Account::Expense:
+ case eMyMoney::Account::Liability:
while (column < m_numColumns) {
it_row.value()[eBudgetDiff][column] = it_row.value()[eBudget][column] - it_row.value()[eActual][column];
++column;
}
break;
default:
break;
}
++it_row;
}
++it_innergroup;
}
++it_outergroup;
}
}
void PivotTable::calculateForecast()
{
//setup forecast
MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast();
//since this is a net worth forecast we want to include all account even those that are not in use
forecast.setIncludeUnusedAccounts(true);
//setup forecast dates
if (m_endDate > QDate::currentDate()) {
forecast.setForecastEndDate(m_endDate);
forecast.setForecastStartDate(QDate::currentDate());
forecast.setForecastDays(QDate::currentDate().daysTo(m_endDate));
} else {
forecast.setForecastStartDate(m_beginDate);
forecast.setForecastEndDate(m_endDate);
forecast.setForecastDays(m_beginDate.daysTo(m_endDate) + 1);
}
//adjust history dates if beginning date is before today
if (m_beginDate < QDate::currentDate()) {
forecast.setHistoryEndDate(m_beginDate.addDays(-1));
forecast.setHistoryStartDate(forecast.historyEndDate().addDays(-forecast.accountsCycle()*forecast.forecastCycles()));
}
//run forecast
if (m_config.rowType() == MyMoneyReport::eAssetLiability) { //asset and liability
forecast.doForecast();
} else { //income and expenses
MyMoneyBudget budget;
forecast.createBudget(budget, m_beginDate.addYears(-1), m_beginDate.addDays(-1), m_beginDate, m_endDate, false);
}
//go through the data and add forecast
PivotGrid::iterator it_outergroup = m_grid.begin();
while (it_outergroup != m_grid.end()) {
PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
int column = m_startColumn;
QDate forecastDate = m_beginDate;
//check whether columns are days or months
if (m_config.isColumnsAreDays()) {
while (column < m_numColumns) {
it_row.value()[eForecast][column] = forecast.forecastBalance(it_row.key(), forecastDate);
forecastDate = forecastDate.addDays(1);
++column;
}
} else {
//if columns are months
while (column < m_numColumns) {
// the forecast balance is on the first day of the month see MyMoneyForecast::calculateScheduledMonthlyBalances()
forecastDate = QDate(forecastDate.year(), forecastDate.month(), 1);
//check that forecastDate is not over ending date
if (forecastDate > m_endDate)
forecastDate = m_endDate;
//get forecast balance and set the corresponding column
it_row.value()[eForecast][column] = forecast.forecastBalance(it_row.key(), forecastDate);
forecastDate = forecastDate.addMonths(1);
++column;
}
}
++it_row;
}
++it_innergroup;
}
++it_outergroup;
}
}
void PivotTable::loadRowTypeList()
{
if ((m_config.isIncludingBudgetActuals()) ||
(!m_config.hasBudget()
&& !m_config.isIncludingForecast()
&& !m_config.isIncludingMovingAverage()
&& !m_config.isIncludingPrice()
&& !m_config.isIncludingAveragePrice())
) {
m_rowTypeList.append(eActual);
m_columnTypeHeaderList.append(i18n("Actual"));
}
if (m_config.hasBudget()) {
m_rowTypeList.append(eBudget);
m_columnTypeHeaderList.append(i18n("Budget"));
}
if (m_config.isIncludingBudgetActuals()) {
m_rowTypeList.append(eBudgetDiff);
m_columnTypeHeaderList.append(i18n("Difference"));
}
if (m_config.isIncludingForecast()) {
m_rowTypeList.append(eForecast);
m_columnTypeHeaderList.append(i18n("Forecast"));
}
if (m_config.isIncludingMovingAverage()) {
m_rowTypeList.append(eAverage);
m_columnTypeHeaderList.append(i18n("Moving Average"));
}
if (m_config.isIncludingAveragePrice()) {
m_rowTypeList.append(eAverage);
m_columnTypeHeaderList.append(i18n("Moving Average Price"));
}
if (m_config.isIncludingPrice()) {
m_rowTypeList.append(ePrice);
m_columnTypeHeaderList.append(i18n("Price"));
}
}
void PivotTable::calculateMovingAverage()
{
int delta = m_config.movingAverageDays() / 2;
//go through the data and add the moving average
PivotGrid::iterator it_outergroup = m_grid.begin();
while (it_outergroup != m_grid.end()) {
PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
int column = m_startColumn;
//check whether columns are days or months
if (m_config.columnType() == MyMoneyReport::eDays) {
while (column < m_numColumns) {
MyMoneyMoney totalPrice = MyMoneyMoney();
QDate averageStart = columnDate(column).addDays(-delta);
QDate averageEnd = columnDate(column).addDays(delta);
for (QDate averageDate = averageStart; averageDate <= averageEnd; averageDate = averageDate.addDays(1)) {
if (m_config.isConvertCurrency()) {
totalPrice += it_row.key().deepCurrencyPrice(averageDate) * it_row.key().baseCurrencyPrice(averageDate);
} else {
totalPrice += it_row.key().deepCurrencyPrice(averageDate);
}
totalPrice = totalPrice.convert(10000);
}
//calculate the average price
MyMoneyMoney averagePrice = totalPrice / MyMoneyMoney((averageStart.daysTo(averageEnd) + 1), 1);
//get the actual value, multiply by the average price and save that value
MyMoneyMoney averageValue = it_row.value()[eActual][column] * averagePrice;
it_row.value()[eAverage][column] = averageValue.convert(10000);
++column;
}
} else {
//if columns are months
while (column < m_numColumns) {
QDate averageStart = columnDate(column);
//set the right start date depending on the column type
switch (m_config.columnType()) {
case MyMoneyReport::eYears: {
averageStart = QDate(columnDate(column).year(), 1, 1);
break;
}
case MyMoneyReport::eBiMonths: {
averageStart = QDate(columnDate(column).year(), columnDate(column).month(), 1).addMonths(-1);
break;
}
case MyMoneyReport::eQuarters: {
averageStart = QDate(columnDate(column).year(), columnDate(column).month(), 1).addMonths(-1);
break;
}
case MyMoneyReport::eMonths: {
averageStart = QDate(columnDate(column).year(), columnDate(column).month(), 1);
break;
}
case MyMoneyReport::eWeeks: {
averageStart = columnDate(column).addDays(-columnDate(column).dayOfWeek() + 1);
break;
}
default:
break;
}
//gather the actual data and calculate the average
MyMoneyMoney totalPrice = MyMoneyMoney();
QDate averageEnd = columnDate(column);
for (QDate averageDate = averageStart; averageDate <= averageEnd; averageDate = averageDate.addDays(1)) {
if (m_config.isConvertCurrency()) {
totalPrice += it_row.key().deepCurrencyPrice(averageDate) * it_row.key().baseCurrencyPrice(averageDate);
} else {
totalPrice += it_row.key().deepCurrencyPrice(averageDate);
}
totalPrice = totalPrice.convert(10000);
}
MyMoneyMoney averagePrice = totalPrice / MyMoneyMoney((averageStart.daysTo(averageEnd) + 1), 1);
MyMoneyMoney averageValue = it_row.value()[eActual][column] * averagePrice;
//fill in the average
it_row.value()[eAverage][column] = averageValue.convert(10000);
++column;
}
}
++it_row;
}
++it_innergroup;
}
++it_outergroup;
}
}
void PivotTable::fillBasePriceUnit(ERowType rowType)
{
MyMoneyFile* file = MyMoneyFile::instance();
QString baseCurrencyId = file->baseCurrency().id();
//get the first price date for securities
QMap<QString, QDate> securityDates = securityFirstPrice();
//go through the data
PivotGrid::iterator it_outergroup = m_grid.begin();
while (it_outergroup != m_grid.end()) {
PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
int column = m_startColumn;
//if it is a base currency fill all the values
bool firstPriceExists = false;
if (it_row.key().currencyId() == baseCurrencyId) {
firstPriceExists = true;
}
while (column < m_numColumns) {
//check whether the date for that column is on or after the first price
if (!firstPriceExists
&& securityDates.contains(it_row.key().currencyId())
&& columnDate(column) >= securityDates.value(it_row.key().currencyId())) {
firstPriceExists = true;
}
//only add the dummy value if there is a price for that date
if (firstPriceExists) {
//insert a unit of currency for each account
it_row.value()[rowType][column] = MyMoneyMoney::ONE;
}
++column;
}
++it_row;
}
++it_innergroup;
}
++it_outergroup;
}
}
QMap<QString, QDate> PivotTable::securityFirstPrice()
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyPriceList priceList = file->priceList();
QMap<QString, QDate> securityPriceDate;
MyMoneyPriceList::const_iterator prices_it;
for (prices_it = priceList.constBegin(); prices_it != priceList.constEnd(); ++prices_it) {
MyMoneyPrice firstPrice = (*((*prices_it).constBegin()));
//check the security in the from field
//if it is there, check if it is older
if (securityPriceDate.contains(firstPrice.from())) {
if (securityPriceDate.value(firstPrice.from()) > firstPrice.date()) {
securityPriceDate[firstPrice.from()] = firstPrice.date();
}
} else {
securityPriceDate.insert(firstPrice.from(), firstPrice.date());
}
//check the security in the to field
//if it is there, check if it is older
if (securityPriceDate.contains(firstPrice.to())) {
if (securityPriceDate.value(firstPrice.to()) > firstPrice.date()) {
securityPriceDate[firstPrice.to()] = firstPrice.date();
}
} else {
securityPriceDate.insert(firstPrice.to(), firstPrice.date());
}
}
return securityPriceDate;
}
void PivotTable::includeInvestmentSubAccounts()
{
// if we're not in expert mode, we need to make sure
// that all stock accounts for the selected investment
// account are also selected
QStringList accountList;
if (m_config.accounts(accountList)) {
if (!KMyMoneyGlobalSettings::expertMode()) {
QStringList::const_iterator it_a, it_b;
for (it_a = accountList.constBegin(); it_a != accountList.constEnd(); ++it_a) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a);
- if (acc.accountType() == MyMoneyAccount::Investment) {
+ if (acc.accountType() == eMyMoney::Account::Investment) {
for (it_b = acc.accountList().constBegin(); it_b != acc.accountList().constEnd(); ++it_b) {
if (!accountList.contains(*it_b)) {
m_config.addAccount(*it_b);
}
}
}
}
}
}
}
int PivotTable::currentDateColumn()
{
//return -1 if the columns do not include the current date
if (m_beginDate > QDate::currentDate() || m_endDate < QDate::currentDate()) {
return -1;
}
//check the date of each column and return if it is the one for the current date
//if columns are not days, return the one for the current month or year
int column = m_startColumn;
while (column < m_numColumns) {
if (columnDate(column) >= QDate::currentDate()) {
break;
}
column++;
}
//if there is no column matching the current date, return -1
if (column == m_numColumns) {
column = -1;
}
return column;
}
} // namespace
diff --git a/kmymoney/reports/pivottable.h b/kmymoney/reports/pivottable.h
index 8fb73dd75..8df366d9b 100644
--- a/kmymoney/reports/pivottable.h
+++ b/kmymoney/reports/pivottable.h
@@ -1,370 +1,370 @@
/***************************************************************************
pivottable.h
-------------------
begin : Sat May 22 2004
copyright : (C) 2004-2005 by Ace Jones <ace.j@hotpop.com>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Alvaro Soliverez <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 PIVOTTABLE_H
#define PIVOTTABLE_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QMap>
#include <QList>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyreport.h"
#include "reporttable.h"
#include "pivotgrid.h"
#include "reportaccount.h"
namespace reports { class KReportChartView; }
namespace reports
{
/**
* Calculates a 'pivot table' of information about the transaction database.
* Based on pivot tables in MS Excel, and implemented as 'Data Pilot' in
* OpenOffice.Org Calc.
*
* | Month,etc
* -------------+------------
* Expense Type | Sum(Value)
* Category |
*
* This is a middle-layer class, between the UI and the engine. The
* MyMoneyReport class holds only the CONFIGURATION parameters. This
* class actually does the work of retrieving the data from the engine
* and formatting it for the user.
*
* @author Ace Jones
*
* @short
**/
class PivotTable : public ReportTable
{
KMM_MYMONEY_UNIT_TESTABLE
public:
/**
* Create a Pivot table style report
*
* @param _report The configuration parameters for this report
*/
PivotTable(const MyMoneyReport& _report);
/**
* virtual Destructur
*/
virtual ~PivotTable() {}
/**
* Render the report body to an HTML stream.
*
* @return QString HTML string representing the report body
*/
QString renderHTML() const;
/**
* Render the report to a comma-separated-values stream.
*
* @return QString CSV string representing the report
*/
QString renderCSV() const;
/**
* Render the report to a graphical chart
*
* @param view The KReportChartView into which to draw the chart.
*/
void drawChart(KReportChartView& view) const;
/**
* Dump the report's HTML to a file
*
* @param file The filename to dump into
* @param context unused, but provided for interface compatibility
*/
void dump(const QString& file, const QString& context = QString()) const;
/**
* Returns the grid generated by the report
*
*/
PivotGrid grid() {
return m_grid;
}
protected:
void init(); // used for debugging the constructor
private:
PivotGrid m_grid;
QStringList m_columnHeadings;
int m_numColumns;
QDate m_beginDate;
QDate m_endDate;
bool m_runningSumsCalculated;
int m_startColumn;
/**
* For budget-vs-actual reports only, maps each account to the account which holds
* the budget for it. If an account is not contained in this map, it is not included
* in the budget.
*/
QMap<QString, QString> m_budgetMap;
/**
* This list contains the types of PivotGridRows that are going to be shown in the report
*/
QList<ERowType> m_rowTypeList;
/**
* This list contains the i18n headers for the column types
*/
QStringList m_columnTypeHeaderList;
/**
* This method returns the formatted value of @a amount with
* a possible @a currencySymbol added and @a prec fractional digits.
* @a currencySymbol defaults to be empty and @a prec defaults to 2.
*
* If @a amount is negative the formatted value is enclosed in an
* HTML font tag to modify the color to reflect the user settings for
* negtive numbers.
*
* Example: 1.23 is returned as '1.23' whereas -1.23 is returned as
* @verbatim <font color="rgb($red,$green,$blue)">-1.23</font>@endverbatim
* with $red, $green and $blue being the actual value for the
* chosen color.
*/
QString coloredAmount(const MyMoneyMoney& amount, const QString& currencySymbol, int prec) const;
protected:
/**
* Creates a row in the grid if it doesn't already exist
*
* Downsteam assignment functions will assume that this row already
* exists, so this function creates a row of the needed length populated
* with zeros.
*
* @param outergroup The outer row group
* @param row The row itself
* @param recursive Whether to also recursively create rows for our parent accounts
*/
void createRow(const QString& outergroup, const ReportAccount& row, bool recursive);
/**
* Assigns a value into the grid
*
* Adds the given value to the value which already exists at the specified grid position
*
* @param outergroup The outer row group
* @param row The row itself
* @param column The column
* @param value The value to be added in
* @param budget Whether this is a budget value (@p true) or an actual
* value (@p false). Defaults to @p false.
* @param stockSplit Whether this is a stock split (@p true) or an actual
* value (@p false). Defaults to @p false.
*/
inline void assignCell(const QString& outergroup, const ReportAccount& row, int column, MyMoneyMoney value, bool budget = false, bool stockSplit = false);
/**
* Create a row for each included account. This is used when
* the config parameter isIncludingUnusedAccount() is true
*/
void createAccountRows();
/**
* Record the opening balances of all qualifying accounts into the grid.
*
* For accounts opened before the report period, places the balance into the '0' column.
* For those opened during the report period, places the balance into the appropriate column
* for the month when it was opened.
*/
void calculateOpeningBalances();
/**
* Calculate budget mapping
*
* For budget-vs-actual reports, this creates a mapping between each account
* in the user's hierarchy and the account where the budget is held for it.
* This is needed because the user can budget on a given account for that
* account and all its descendants. Also if NO budget is placed on the
* account or any of its parents, the account is not included in the map.
*/
void calculateBudgetMapping();
/**
* Calculate the running sums.
*
* After calling this method, each cell of the report will contain the running sum of all
* the cells in its row in this and earlier columns.
*
* For example, consider a row with these values:
* 01 02 03 04 05 06 07 08 09 10
*
* After calling this function, the row will look like this:
* 01 03 06 10 15 21 28 36 45 55
*/
void calculateRunningSums();
void calculateRunningSums(PivotInnerGroup::iterator& it_row);
/**
* This method calculates the difference between a @a budgeted and an @a
* actual amount. The calculation is based on the type of the
* @a repAccount. The difference value is calculated as follows:
*
- * If @a repAccount is of type MyMoneyAccount::Income
+ * If @a repAccount is of type eMyMoney::Account::Income
*
* @code
* diff = actual - budgeted
* @endcode
*
- * If @a repAccount is of type MyMoneyAccount::Expense
+ * If @a repAccount is of type eMyMoney::Account::Expense
*
* @code
* diff = budgeted - actual
* @endcode
*
* In all other cases, 0 is returned.
*/
void calculateBudgetDiff();
/**
* This method calculates forecast for a report
*/
void calculateForecast();
/**
* This method inserts units to be used to display prices
*/
void fillBasePriceUnit(ERowType rowType);
/**
* This method collects the first date for which there is a price for every security
*/
QMap<QString, QDate> securityFirstPrice();
/**
* This method calculates moving average for a report
*/
void calculateMovingAverage();
/**
* Calculate the row and column totals
*
* This function will set the m_total members of all the TGrid objects. Be sure the values are
* all converted to the base currency first!!
*
*/
void calculateTotals();
/**
* Convert each value in the grid to the base currency
*
*/
void convertToBaseCurrency();
/**
* Convert each value in the grid to the account/category's deep currency
*
* See AccountDescriptor::deepCurrencyPrice() for a description of 'deep' currency
*
*/
void convertToDeepCurrency();
/**
* Turn month-long columns into larger time periods if needed
*
* For example, consider a row with these values:
* 01 02 03 04 05 06 07 08 09 10
*
* If the column pitch is 3 (i.e. quarterly), after calling this function,
* the row will look like this:
* 06 15 26 10
*/
void collapseColumns();
/**
* Determine the proper column headings based on the time periods covered by each column
*
*/
void calculateColumnHeadings();
/**
* Helper methods for collapseColumns
*
*/
void accumulateColumn(int destcolumn, int sourcecolumn);
void clearColumn(int column);
/**
* Calculate the column of a given date. This is the absolute column in a
* hypothetical report that covers all of known time. In reality an actual
* report will be a subset of that.
*
* @param _date The date
*/
int columnValue(const QDate& _date) const;
/**
* Calculate the date of the last day covered by a given column.
*
* @param column The column
*/
QDate columnDate(int column) const;
/**
* Returns the balance of a given cell. Throws an exception once calculateRunningSums() has been run.
*/
MyMoneyMoney cellBalance(const QString& outergroup, const ReportAccount& _row, int column, bool budget);
/**
* Draws a PivotGridRowSet in a chart for the given ERowType
*/
unsigned drawChartRowSet(int rowNum, const bool seriesTotals, const bool accountSeries, KReportChartView& chartView, const PivotGridRowSet& rowSet, const ERowType rowType) const;
/**
* Loads m_rowTypeList with the list of PivotGridRow types that the reporttable
* should show
*/
void loadRowTypeList();
/**
* If not in expert mode, include all subaccounts for each selected
* investment account
*/
void includeInvestmentSubAccounts();
/**
* Returns the column which holds the current date
* Returns -1 if the current date is not within range
*/
int currentDateColumn();
};
}
#endif
// PIVOTTABLE_H
diff --git a/kmymoney/reports/querytable.cpp b/kmymoney/reports/querytable.cpp
index 85b725d09..589af8780 100644
--- a/kmymoney/reports/querytable.cpp
+++ b/kmymoney/reports/querytable.cpp
@@ -1,2160 +1,2165 @@
/***************************************************************************
querytable.cpp
-------------------
begin : Fri Jul 23 2004
copyright : (C) 2004-2005 by Ace Jones <acejones@users.sourceforge.net>
(C) 2007 Sascha Pfau <MrPeacock@gmail.com>
(C) 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
***************************************************************************/
/****************************************************************************
Contains code from the func_xirr and related methods of financial.cpp
- KOffice 1.6 by Sascha Pfau. Sascha agreed to relicense those methods under
GPLv2 or later.
*****************************************************************************/
/***************************************************************************
* *
* 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.h"
#include <cmath>
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
#include <QDebug>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneyaccount.h"
+#include "mymoneyinstitution.h"
+#include "mymoneyprice.h"
+#include "mymoneypayee.h"
+#include "mymoneytag.h"
#include "mymoneytransaction.h"
#include "mymoneyreport.h"
#include "mymoneyexception.h"
#include "kmymoneyutils.h"
#include "reportaccount.h"
namespace reports
{
// ****************************************************************************
//
// CashFlowListItem implementation
//
// Cash flow analysis tools for investment reports
//
// ****************************************************************************
QDate CashFlowListItem::m_sToday = QDate::currentDate();
MyMoneyMoney CashFlowListItem::NPV(double _rate) const
{
double T = static_cast<double>(m_sToday.daysTo(m_date)) / 365.0;
MyMoneyMoney result(m_value.toDouble() / pow(1 + _rate, T), 100);
//qDebug() << "CashFlowListItem::NPV( " << _rate << " ) == " << result;
return result;
}
// ****************************************************************************
//
// CashFlowList implementation
//
// Cash flow analysis tools for investment reports
//
// ****************************************************************************
CashFlowListItem CashFlowList::mostRecent() const
{
CashFlowList dupe(*this);
qSort(dupe);
//qDebug() << " CashFlowList::mostRecent() == " << dupe.back().date().toString(Qt::ISODate);
return dupe.back();
}
MyMoneyMoney CashFlowList::NPV(double _rate) const
{
MyMoneyMoney result;
const_iterator it_cash = constBegin();
while (it_cash != constEnd()) {
result += (*it_cash).NPV(_rate);
++it_cash;
}
//qDebug() << "CashFlowList::NPV( " << _rate << " ) == " << result << "------------------------" << endl;
return result;
}
double CashFlowList::calculateXIRR() const
{
double resultRate = 0.00001;
double resultZero = 0.00000;
//if ( args.count() > 2 )
// resultRate = calc->conv()->asFloat ( args[2] ).asFloat();
// check pairs and count >= 2 and guess > -1.0
//if ( args[0].count() != args[1].count() || args[1].count() < 2 || resultRate <= -1.0 )
// return Value::errorVALUE();
// define max epsilon
static const double maxEpsilon = 1e-5;
// max number of iterations
static const int maxIter = 50;
// Newton's method - try to find a res, with a accuracy of maxEpsilon
double rateEpsilon, newRate, resultValue;
int i = 0;
bool contLoop;
do {
resultValue = xirrResult(resultRate);
double resultDerive = xirrResultDerive(resultRate);
//check what happens if xirrResultDerive is zero
//Don't know if it is correct to dismiss the result
if (resultDerive != 0) {
newRate = resultRate - resultValue / resultDerive;
} else {
newRate = resultRate - resultValue;
}
rateEpsilon = fabs(newRate - resultRate);
resultRate = newRate;
contLoop = (rateEpsilon > maxEpsilon) && (fabs(resultValue) > maxEpsilon);
} while (contLoop && (++i < maxIter));
if (contLoop)
return resultZero;
return resultRate;
}
double CashFlowList::xirrResult(double& rate) const
{
QDate date;
double r = rate + 1.0;
double res = 0.00000;//back().value().toDouble();
QList<CashFlowListItem>::const_iterator list_it = constBegin();
while (list_it != constEnd()) {
double e_i = ((* list_it).today().daysTo((* list_it).date())) / 365.0;
MyMoneyMoney val = (* list_it).value();
if (e_i < 0) {
res += val.toDouble() * pow(r, -e_i);
} else {
res += val.toDouble() / pow(r, e_i);
}
++list_it;
}
return res;
}
double CashFlowList::xirrResultDerive(double& rate) const
{
QDate date;
double r = rate + 1.0;
double res = 0.00000;
QList<CashFlowListItem>::const_iterator list_it = constBegin();
while (list_it != constEnd()) {
double e_i = ((* list_it).today().daysTo((* list_it).date())) / 365.0;
MyMoneyMoney val = (* list_it).value();
res -= e_i * val.toDouble() / pow(r, e_i + 1.0);
++list_it;
}
return res;
}
double CashFlowList::IRR() const
{
double result = 0.0;
// set 'today', which is the most recent of all dates in the list
CashFlowListItem::setToday(mostRecent().date());
result = calculateXIRR();
return result;
}
MyMoneyMoney CashFlowList::total() const
{
MyMoneyMoney result;
const_iterator it_cash = constBegin();
while (it_cash != constEnd()) {
result += (*it_cash).value();
++it_cash;
}
return result;
}
void CashFlowList::dumpDebug() const
{
const_iterator it_item = constBegin();
while (it_item != constEnd()) {
qDebug() << (*it_item).date().toString(Qt::ISODate) << " " << (*it_item).value().toString();
++it_item;
}
}
// ****************************************************************************
//
// QueryTable implementation
//
// ****************************************************************************
/**
* TODO
*
* - Collapse 2- & 3- groups when they are identical
* - Way more test cases (especially splits & transfers)
* - Option to collapse splits
* - Option to exclude transfers
*
*/
QueryTable::QueryTable(const MyMoneyReport& _report): ListTable(_report)
{
// separated into its own method to allow debugging (setting breakpoints
// directly in ctors somehow does not work for me (ipwizard))
// TODO: remove the init() method and move the code back to the ctor
init();
}
void QueryTable::init()
{
m_columns.clear();
m_group.clear();
m_subtotal.clear();
m_postcolumns.clear();
switch (m_config.rowType()) {
case MyMoneyReport::eAccountByTopAccount:
case MyMoneyReport::eEquityType:
case MyMoneyReport::eAccountType:
case MyMoneyReport::eInstitution:
constructAccountTable();
m_columns << ctAccount;
break;
case MyMoneyReport::eAccount:
constructTransactionTable();
m_columns << ctAccountID << ctPostDate;
break;
case MyMoneyReport::ePayee:
case MyMoneyReport::eTag:
case MyMoneyReport::eMonth:
case MyMoneyReport::eWeek:
constructTransactionTable();
m_columns << ctPostDate << ctAccount;
break;
case MyMoneyReport::eCashFlow:
constructSplitsTable();
m_columns << ctPostDate;
break;
default:
constructTransactionTable();
m_columns << ctPostDate;
}
// Sort the data to match the report definition
m_subtotal << ctValue;
switch (m_config.rowType()) {
case MyMoneyReport::eCashFlow:
m_group << ctCategoryType << ctTopCategory << ctCategory;
break;
case MyMoneyReport::eCategory:
m_group << ctCategoryType << ctTopCategory << ctCategory;
break;
case MyMoneyReport::eTopCategory:
m_group << ctCategoryType << ctTopCategory;
break;
case MyMoneyReport::eTopAccount:
m_group << ctTopAccount << ctAccount;
break;
case MyMoneyReport::eAccount:
m_group << ctAccount;
break;
case MyMoneyReport::eAccountReconcile:
m_group << ctAccount << ctReconcileFlag;
break;
case MyMoneyReport::ePayee:
m_group << ctPayee;
break;
case MyMoneyReport::eTag:
m_group << ctTag;
break;
case MyMoneyReport::eMonth:
m_group << ctMonth;
break;
case MyMoneyReport::eWeek:
m_group << ctWeek;
break;
case MyMoneyReport::eAccountByTopAccount:
m_group << ctTopAccount;
break;
case MyMoneyReport::eEquityType:
m_group << ctEquityType;
break;
case MyMoneyReport::eAccountType:
m_group << ctType;
break;
case MyMoneyReport::eInstitution:
m_group << ctInstitution << ctTopAccount;
break;
default:
throw MYMONEYEXCEPTION("QueryTable::QueryTable(): unhandled row type");
}
QVector<cellTypeE> sort = QVector<cellTypeE>::fromList(m_group) << QVector<cellTypeE>::fromList(m_columns) << ctID << ctRank;
m_columns.clear();
switch (m_config.rowType()) {
case MyMoneyReport::eAccountByTopAccount:
case MyMoneyReport::eEquityType:
case MyMoneyReport::eAccountType:
case MyMoneyReport::eInstitution:
m_columns << ctAccount;
break;
default:
m_columns << ctPostDate;
}
unsigned qc = m_config.queryColumns();
if (qc & MyMoneyReport::eQCnumber)
m_columns << ctNumber;
if (qc & MyMoneyReport::eQCpayee)
m_columns << ctPayee;
if (qc & MyMoneyReport::eQCtag)
m_columns << ctTag;
if (qc & MyMoneyReport::eQCcategory)
m_columns << ctCategory;
if (qc & MyMoneyReport::eQCaccount)
m_columns << ctAccount;
if (qc & MyMoneyReport::eQCreconciled)
m_columns << ctReconcileFlag;
if (qc & MyMoneyReport::eQCmemo)
m_columns << ctMemo;
if (qc & MyMoneyReport::eQCaction)
m_columns << ctAction;
if (qc & MyMoneyReport::eQCshares)
m_columns << ctShares;
if (qc & MyMoneyReport::eQCprice)
m_columns << ctPrice;
if (qc & MyMoneyReport::eQCperformance) {
m_subtotal.clear();
switch (m_config.investmentSum()) {
case MyMoneyReport::eSumOwnedAndSold:
m_columns << ctBuys << ctSells << ctReinvestIncome << ctCashIncome
<< ctEndingBalance << ctReturn << ctReturnInvestment;
m_subtotal << ctBuys << ctSells << ctReinvestIncome << ctCashIncome
<< ctEndingBalance << ctReturn << ctReturnInvestment;
break;
case MyMoneyReport::eSumOwned:
m_columns << ctBuys << ctReinvestIncome << ctMarketValue
<< ctReturn << ctReturnInvestment;
m_subtotal << ctBuys << ctReinvestIncome << ctMarketValue
<< ctReturn << ctReturnInvestment;
break;
case MyMoneyReport::eSumSold:
m_columns << ctBuys << ctSells << ctCashIncome
<< ctReturn << ctReturnInvestment;
m_subtotal << ctBuys << ctSells << ctCashIncome
<< ctReturn << ctReturnInvestment;
break;
case MyMoneyReport::eSumPeriod:
default:
m_columns << ctStartingBalance << ctBuys << ctSells
<< ctReinvestIncome << ctCashIncome << ctEndingBalance
<< ctReturn << ctReturnInvestment;
m_subtotal << ctStartingBalance << ctBuys << ctSells
<< ctReinvestIncome << ctCashIncome << ctEndingBalance
<< ctReturn << ctReturnInvestment;
break;
}
}
if (qc & MyMoneyReport::eQCcapitalgain) {
m_subtotal.clear();
switch (m_config.investmentSum()) {
case MyMoneyReport::eSumOwned:
m_columns << ctShares << ctBuyPrice << ctLastPrice
<< ctBuys << ctMarketValue << ctPercentageGain
<< ctCapitalGain;
m_subtotal << ctShares << ctBuyPrice << ctLastPrice
<< ctBuys << ctMarketValue << ctPercentageGain
<< ctCapitalGain;
break;
case MyMoneyReport::eSumSold:
default:
m_columns << ctBuys << ctSells << ctCapitalGain;
m_subtotal << ctBuys << ctSells << ctCapitalGain;
if (m_config.isShowingSTLTCapitalGains()) {
m_columns << ctBuysST << ctSellsST << ctCapitalGainST
<< ctBuysLT << ctSellsLT << ctCapitalGainLT;
m_subtotal << ctBuysST << ctSellsST << ctCapitalGainST
<< ctBuysLT << ctSellsLT << ctCapitalGainLT;
}
break;
}
}
if (qc & MyMoneyReport::eQCloan) {
m_columns << ctPayment << ctInterest << ctFees;
m_postcolumns << ctBalance;
}
if (qc & MyMoneyReport::eQCbalance)
m_postcolumns << ctBalance;
TableRow::setSortCriteria(sort);
qSort(m_rows);
if (m_config.isShowingColumnTotals())
constructTotalRows(); // adds total rows to m_rows
}
void QueryTable::constructTotalRows()
{
if (m_rows.isEmpty())
return;
// qSort places grand total at last position, because it doesn't belong to any group
for (int i = 0; i < m_rows.count(); ++i) {
if (m_rows.at(0)[ctRank] == QLatin1String("4") || m_rows.at(0)[ctRank] == QLatin1String("5")) // it should be unlikely that total row is at the top of rows, so...
m_rows.move(0, m_rows.count() - 1 - i); // ...move it at the bottom
else
break;
}
MyMoneyFile* file = MyMoneyFile::instance();
QList<cellTypeE> subtotals = m_subtotal;
QList<cellTypeE> groups = m_group;
QList<cellTypeE> columns = m_columns;
if (!m_subtotal.isEmpty() && subtotals.count() == 1)
columns.append(m_subtotal);
QList<cellTypeE> postcolumns = m_postcolumns;
if (!m_postcolumns.isEmpty())
columns.append(postcolumns);
QMap<QString, QList<QMap<cellTypeE, MyMoneyMoney>>> totalCurrency;
QList<QMap<cellTypeE, MyMoneyMoney>> totalGroups;
QMap<cellTypeE, MyMoneyMoney> totalsValues;
// initialize all total values under summed columns to be zero
foreach (auto subtotal, subtotals) {
totalsValues.insert(subtotal, MyMoneyMoney());
}
totalsValues.insert(ctRowsCount, MyMoneyMoney());
// create total groups containing totals row for each group
totalGroups.append(totalsValues); // prepend with extra group for grand total
for (int j = 0; j < groups.count(); ++j) {
totalGroups.append(totalsValues);
}
QList<TableRow> stashedTotalRows;
int iCurrentRow, iNextRow;
for (iCurrentRow = 0; iCurrentRow < m_rows.count();) {
iNextRow = iCurrentRow + 1;
// total rows are useless at summing so remove whole block of them at once
while (iNextRow != m_rows.count() && (m_rows.at(iNextRow).value(ctRank) == QLatin1String("4") || m_rows.at(iNextRow).value(ctRank) == QLatin1String("5"))) {
stashedTotalRows.append(m_rows.takeAt(iNextRow)); // ...but stash them just in case
}
bool lastRow = (iNextRow == m_rows.count());
// sum all subtotal values for lowest group
QString currencyID = m_rows.at(iCurrentRow).value(ctCurrency);
if (m_rows.at(iCurrentRow).value(ctRank) == QLatin1String("1")) { // don't sum up on balance (rank = 0 || rank = 3) and minor split (rank = 2)
foreach (auto subtotal, subtotals) {
if (!totalCurrency.contains(currencyID))
totalCurrency[currencyID].append(totalGroups);
totalCurrency[currencyID].last()[subtotal] += MyMoneyMoney(m_rows.at(iCurrentRow)[subtotal]);
}
totalCurrency[currencyID].last()[ctRowsCount] += MyMoneyMoney::ONE;
}
// iterate over groups from the lowest to the highest to find group change
for (int i = groups.count() - 1; i >= 0 ; --i) {
// if any of groups from next row changes (or next row is the last row), then it's time to put totals row
if (lastRow || m_rows.at(iCurrentRow)[groups.at(i)] != m_rows.at(iNextRow)[groups.at(i)]) {
bool isMainCurrencyTotal = true;
QMap<QString, QList<QMap<cellTypeE, MyMoneyMoney>>>::iterator currencyGrp = totalCurrency.begin();
while (currencyGrp != totalCurrency.end()) {
if (!MyMoneyMoney((*currencyGrp).at(i + 1).value(ctRowsCount)).isZero()) { // if no rows summed up, then no totals row
TableRow totalsRow;
// sum all subtotal values for higher groups (excluding grand total) and reset lowest group values
QMap<cellTypeE, MyMoneyMoney>::iterator upperGrp = (*currencyGrp)[i].begin();
QMap<cellTypeE, MyMoneyMoney>::iterator lowerGrp = (*currencyGrp)[i + 1].begin();
while(upperGrp != (*currencyGrp)[i].end()) {
totalsRow[lowerGrp.key()] = lowerGrp.value().toString(); // fill totals row with subtotal values...
(*upperGrp) += (*lowerGrp);
// (*lowerGrp) = MyMoneyMoney();
++upperGrp;
++lowerGrp;
}
// custom total values calculations
foreach (auto subtotal, subtotals) {
if (subtotal == ctReturnInvestment)
totalsRow[subtotal] = helperROI((*currencyGrp).at(i + 1).value(ctBuys) - (*currencyGrp).at(i + 1).value(ctReinvestIncome), (*currencyGrp).at(i + 1).value(ctSells),
(*currencyGrp).at(i + 1).value(ctStartingBalance), (*currencyGrp).at(i + 1).value(ctEndingBalance) + (*currencyGrp).at(i + 1).value(ctMarketValue),
(*currencyGrp).at(i + 1).value(ctCashIncome)).toString();
else if (subtotal == ctPercentageGain)
totalsRow[subtotal] = (((*currencyGrp).at(i + 1).value(ctBuys) + (*currencyGrp).at(i + 1).value(ctMarketValue)) / (*currencyGrp).at(i + 1).value(ctBuys).abs()).toString();
else if (subtotal == ctPrice)
totalsRow[subtotal] = MyMoneyMoney((*currencyGrp).at(i + 1).value(ctPrice) / (*currencyGrp).at(i + 1).value(ctRowsCount)).toString();
}
// total values that aren't calculated here, but are taken untouched from external source, e.g. constructPerformanceRow
if (!stashedTotalRows.isEmpty()) {
for (int j = 0; j < stashedTotalRows.count(); ++j) {
if (stashedTotalRows.at(j).value(ctCurrency) != currencyID)
continue;
foreach (auto subtotal, subtotals) {
if (subtotal == ctReturn)
totalsRow[ctReturn] = stashedTotalRows.takeAt(j)[ctReturn];
}
break;
}
}
(*currencyGrp).replace(i + 1, totalsValues);
for (int j = 0; j < groups.count(); ++j) {
totalsRow[groups.at(j)] = m_rows.at(iCurrentRow)[groups.at(j)]; // ...and identification
}
QString currencyID = currencyGrp.key();
if (currencyID.isEmpty() && totalCurrency.count() > 1)
currencyID = file->baseCurrency().id();
totalsRow[ctCurrency] = currencyID;
if (isMainCurrencyTotal) {
totalsRow[ctRank] = QLatin1Char('4');
isMainCurrencyTotal = false;
} else
totalsRow[ctRank] = QLatin1Char('5');
totalsRow[ctDepth] = QString::number(i);
totalsRow.remove(ctRowsCount);
m_rows.insert(iNextRow++, totalsRow); // iCurrentRow and iNextRow can diverge here by more than one
}
++currencyGrp;
}
}
}
// code to put grand total row
if (lastRow) {
bool isMainCurrencyTotal = true;
QMap<QString, QList<QMap<cellTypeE, MyMoneyMoney>>>::iterator currencyGrp = totalCurrency.begin();
while (currencyGrp != totalCurrency.end()) {
TableRow totalsRow;
QMap<cellTypeE, MyMoneyMoney>::const_iterator grandTotalGrp = (*currencyGrp)[0].constBegin();
while(grandTotalGrp != (*currencyGrp)[0].constEnd()) {
totalsRow[grandTotalGrp.key()] = grandTotalGrp.value().toString();
++grandTotalGrp;
}
foreach (auto subtotal, subtotals) {
if (subtotal == ctReturnInvestment)
totalsRow[subtotal] = helperROI((*currencyGrp).at(0).value(ctBuys) - (*currencyGrp).at(0).value(ctReinvestIncome), (*currencyGrp).at(0).value(ctSells),
(*currencyGrp).at(0).value(ctStartingBalance), (*currencyGrp).at(0).value(ctEndingBalance) + (*currencyGrp).at(0).value(ctMarketValue),
(*currencyGrp).at(0).value(ctCashIncome)).toString();
else if (subtotal == ctPercentageGain)
totalsRow[subtotal] = (((*currencyGrp).at(0).value(ctBuys) + (*currencyGrp).at(0).value(ctMarketValue)) / (*currencyGrp).at(0).value(ctBuys).abs()).toString();
else if (subtotal == ctPrice)
totalsRow[subtotal] = MyMoneyMoney((*currencyGrp).at(0).value(ctPrice) / (*currencyGrp).at(0).value(ctRowsCount)).toString();
}
if (!stashedTotalRows.isEmpty()) {
for (int j = 0; j < stashedTotalRows.count(); ++j) {
foreach (auto subtotal, subtotals) {
if (subtotal == ctReturn)
totalsRow[ctReturn] = stashedTotalRows.takeAt(j)[ctReturn];
}
}
}
for (int j = 0; j < groups.count(); ++j) {
totalsRow[groups.at(j)] = QString(); // no identification
}
QString currencyID = currencyGrp.key();
if (currencyID.isEmpty() && totalCurrency.count() > 1)
currencyID = file->baseCurrency().id();
totalsRow[ctCurrency] = currencyID;
if (isMainCurrencyTotal) {
totalsRow[ctRank] = QLatin1Char('4');
isMainCurrencyTotal = false;
} else
totalsRow[ctRank] = QLatin1Char('5');
totalsRow[ctDepth] = QString();
m_rows.append(totalsRow);
++currencyGrp;
}
break; // no use to loop further
}
iCurrentRow = iNextRow; // iCurrent makes here a leap forward by at least one
}
}
void QueryTable::constructTransactionTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
//make sure we have all subaccounts of investment accounts
includeInvestmentSubAccounts();
MyMoneyReport report(m_config);
report.setReportAllSplits(false);
report.setConsiderCategory(true);
bool use_transfers;
bool use_summary;
bool hide_details;
bool tag_special_case = false;
switch (m_config.rowType()) {
case MyMoneyReport::eCategory:
case MyMoneyReport::eTopCategory:
use_summary = false;
use_transfers = false;
hide_details = false;
break;
case MyMoneyReport::ePayee:
use_summary = false;
use_transfers = false;
hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone);
break;
case MyMoneyReport::eTag:
use_summary = false;
use_transfers = false;
hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone);
tag_special_case = true;
break;
default:
use_summary = true;
use_transfers = true;
hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone);
break;
}
// support for opening and closing balances
QMap<QString, MyMoneyAccount> accts;
//get all transactions for this report
QList<MyMoneyTransaction> transactions = file->transactionList(report);
for (QList<MyMoneyTransaction>::const_iterator it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) {
TableRow qA, qS;
QDate pd;
QList<QString> tagIdListCache;
qA[ctID] = qS[ctID] = (* it_transaction).id();
qA[ctEntryDate] = qS[ctEntryDate] = (* it_transaction).entryDate().toString(Qt::ISODate);
qA[ctPostDate] = qS[ctPostDate] = (* it_transaction).postDate().toString(Qt::ISODate);
qA[ctCommodity] = qS[ctCommodity] = (* it_transaction).commodity();
pd = (* it_transaction).postDate();
qA[ctMonth] = qS[ctMonth] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate));
qA[ctWeek] = qS[ctWeek] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate));
if (!m_containsNonBaseCurrency && (*it_transaction).commodity() != file->baseCurrency().id())
m_containsNonBaseCurrency = true;
if (report.isConvertCurrency())
qA[ctCurrency] = qS[ctCurrency] = file->baseCurrency().id();
else
qA[ctCurrency] = qS[ctCurrency] = (*it_transaction).commodity();
// to handle splits, we decide on which account to base the split
// (a reference point or point of view so to speak). here we take the
// first account that is a stock account or loan account (or the first account
// that is not an income or expense account if there is no stock or loan account)
// to be the account (qA) that will have the sub-item "split" entries. we add
// one transaction entry (qS) for each subsequent entry in the split.
const QList<MyMoneySplit>& splits = (*it_transaction).splits();
QList<MyMoneySplit>::const_iterator myBegin, it_split;
for (it_split = splits.constBegin(), myBegin = splits.constEnd(); it_split != splits.constEnd(); ++it_split) {
ReportAccount splitAcc = (* it_split).accountId();
// always put split with a "stock" account if it exists
if (splitAcc.isInvest())
break;
// prefer to put splits with a "loan" account if it exists
if (splitAcc.isLoan())
myBegin = it_split;
if ((myBegin == splits.end()) && ! splitAcc.isIncomeExpense()) {
myBegin = it_split;
}
}
// select our "reference" split
if (it_split == splits.end()) {
it_split = myBegin;
} else {
myBegin = it_split;
}
// skip this transaction if we didn't find a valid base account - see the above description
// for the base account's description - if we don't find it avoid a crash by skipping the transaction
if (myBegin == splits.end())
continue;
// if the split is still unknown, use the first one. I have seen this
// happen with a transaction that has only a single split referencing an income or expense
// account and has an amount and value of 0. Such a transaction will fall through
// the above logic and leave 'it_split' pointing to splits.end() which causes the remainder
// of this to end in an infinite loop.
if (it_split == splits.end()) {
it_split = splits.begin();
}
// for "loan" reports, the loan transaction gets special treatment.
// the splits of a loan transaction are placed on one line in the
// reference (loan) account (qA). however, we process the matching
// split entries (qS) normally.
bool loan_special_case = false;
if (m_config.queryColumns() & MyMoneyReport::eQCloan) {
ReportAccount splitAcc = (*it_split).accountId();
loan_special_case = splitAcc.isLoan();
}
bool include_me = true;
bool transaction_text = false; //indicates whether a text should be considered as a match for the transaction or for a split only
QString a_fullname;
QString a_memo;
int pass = 1;
QString myBeginCurrency;
QString baseCurrency = file->baseCurrency().id();
QMap<QString, MyMoneyMoney> xrMap; // container for conversion rates from given currency to myBeginCurrency
do {
MyMoneyMoney xr;
ReportAccount splitAcc = (* it_split).accountId();
QString splitCurrency;
if (splitAcc.isInvest())
splitCurrency = file->account(file->account((*it_split).accountId()).parentAccountId()).currencyId();
else
splitCurrency = file->account((*it_split).accountId()).currencyId();
if (it_split == myBegin)
myBeginCurrency = splitCurrency;
//get fraction for account
int fraction = splitAcc.currency().smallestAccountFraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = file->baseCurrency().smallestAccountFraction();
QString institution = splitAcc.institutionId();
QString payee = (*it_split).payeeId();
const QList<QString> tagIdList = (*it_split).tagIdList();
//convert to base currency
if (m_config.isConvertCurrency()) {
xr = xrMap.value(splitCurrency, xr); // check if there is conversion rate to myBeginCurrency already stored...
if (xr == MyMoneyMoney()) // ...if not...
xr = (*it_split).price(); // ...take conversion rate to myBeginCurrency from split
else if (splitAcc.isInvest()) // if it's stock split...
xr *= (*it_split).price(); // ...multiply it by stock price stored in split
if (!m_containsNonBaseCurrency && myBeginCurrency != baseCurrency)
m_containsNonBaseCurrency = true;
if (myBeginCurrency != baseCurrency) { // myBeginCurrency can differ from baseCurrency...
MyMoneyPrice price = file->price(myBeginCurrency, baseCurrency,
(*it_transaction).postDate()); // ...so check conversion rate...
if (price.isValid()) {
xr *= price.rate(baseCurrency); // ...and multiply it by current price...
qA[ctCurrency] = qS[ctCurrency] = baseCurrency;
} else
qA[ctCurrency] = qS[ctCurrency] = myBeginCurrency; // ...and set information about non-baseCurrency
}
} else if (splitAcc.isInvest())
xr = (*it_split).price();
else
xr = MyMoneyMoney::ONE;
if (it_split == myBegin) {
include_me = m_config.includes(splitAcc);
if (include_me)
// track accts that will need opening and closing balances
//FIXME in some cases it will show the opening and closing
//balances but no transactions if the splits are all filtered out -- asoliverez
accts.insert(splitAcc.id(), splitAcc);
qA[ctAccount] = splitAcc.name();
qA[ctAccountID] = splitAcc.id();
qA[ctTopAccount] = splitAcc.topParentName();
if (splitAcc.isInvest()) {
// use the institution of the parent for stock accounts
institution = splitAcc.parent().institutionId();
MyMoneyMoney shares = (*it_split).shares();
int pricePrecision = file->security(splitAcc.currencyId()).pricePrecision();
qA[ctAction] = (*it_split).action();
qA[ctShares] = shares.isZero() ? QString() : shares.toString();
qA[ctPrice] = shares.isZero() ? QString() : xr.convertPrecision(pricePrecision).toString();
if (((*it_split).action() == MyMoneySplit::ActionBuyShares) && shares.isNegative())
qA[ctAction] = "Sell";
qA[ctInvestAccount] = splitAcc.parent().name();
MyMoneySplit stockSplit = (*it_split);
MyMoneySplit assetAccountSplit;
QList<MyMoneySplit> feeSplits;
QList<MyMoneySplit> interestSplits;
MyMoneySecurity currency;
MyMoneySecurity security;
MyMoneySplit::investTransactionTypeE transactionType;
KMyMoneyUtils::dissectTransaction((*it_transaction), stockSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType);
if (!(assetAccountSplit == MyMoneySplit())) {
for (it_split = splits.begin(); it_split != splits.end(); ++it_split) {
if ((*it_split) == assetAccountSplit) {
splitAcc = assetAccountSplit.accountId(); // switch over from stock split to asset split because amount in stock split doesn't take fees/interests into account
myBegin = it_split; // set myBegin to asset split, so stock split can be listed in details under splits
myBeginCurrency = (file->account((*myBegin).accountId())).currencyId();
if (!m_containsNonBaseCurrency && myBeginCurrency != baseCurrency)
m_containsNonBaseCurrency = true;
if (m_config.isConvertCurrency()) {
if (myBeginCurrency != baseCurrency) {
MyMoneyPrice price = file->price(myBeginCurrency, baseCurrency, (*it_transaction).postDate());
if (price.isValid()) {
xr = price.rate(baseCurrency);
qA[ctCurrency] = qS[ctCurrency] = baseCurrency;
} else
qA[ctCurrency] = qS[ctCurrency] = myBeginCurrency;
} else
xr = MyMoneyMoney::ONE;
qA[ctPrice] = shares.isZero() ? QString() : (stockSplit.price() * xr / (*it_split).price()).toString();
// put conversion rate for all splits with this currency, so...
// every split of transaction have the same conversion rate
xrMap.insert(splitCurrency, MyMoneyMoney::ONE / (*it_split).price());
} else
xr = (*it_split).price();
break;
}
}
}
} else
qA[ctPrice] = xr.toString();
a_fullname = splitAcc.fullName();
a_memo = (*it_split).memo();
transaction_text = m_config.match(&(*it_split));
qA[ctInstitution] = institution.isEmpty()
? i18n("No Institution")
: file->institution(institution).name();
qA[ctPayee] = payee.isEmpty()
? i18n("[Empty Payee]")
: file->payee(payee).name().simplified();
if (tag_special_case) {
tagIdListCache = tagIdList;
} else {
QString delimiter;
foreach(const auto tagId, tagIdList) {
qA[ctTag] += delimiter + file->tag(tagId).name().simplified();
delimiter = QLatin1Char(',');
}
}
qA[ctReconcileDate] = (*it_split).reconcileDate().toString(Qt::ISODate);
qA[ctReconcileFlag] = KMyMoneyUtils::reconcileStateToString((*it_split).reconcileFlag(), true);
qA[ctNumber] = (*it_split).number();
qA[ctMemo] = a_memo;
qA[ctValue] = ((*it_split).shares() * xr).convert(fraction).toString();
qS[ctReconcileDate] = qA[ctReconcileDate];
qS[ctReconcileFlag] = qA[ctReconcileFlag];
qS[ctNumber] = qA[ctNumber];
qS[ctTopCategory] = splitAcc.topParentName();
qS[ctCategoryType] = i18n("Transfer");
// only include the configured accounts
if (include_me) {
if (loan_special_case) {
// put the principal amount in the "value" column and convert to lowest fraction
qA[ctValue] = (-(*it_split).shares() * xr).convert(fraction).toString();
qA[ctRank] = QLatin1Char('1');
qA[ctSplit].clear();
} else {
if ((splits.count() > 2) && use_summary) {
// add the "summarized" split transaction
// this is the sub-total of the split detail
// convert to lowest fraction
qA[ctRank] = QLatin1Char('1');
qA[ctCategory] = i18n("[Split Transaction]");
qA[ctTopCategory] = i18nc("Split transaction", "Split");
qA[ctCategoryType] = i18nc("Split transaction", "Split");
m_rows += qA;
}
}
}
} else {
if (include_me) {
if (loan_special_case) {
MyMoneyMoney value = (-(* it_split).shares() * xr).convert(fraction);
if ((*it_split).action() == MyMoneySplit::ActionAmortization) {
// put the payment in the "payment" column and convert to lowest fraction
qA[ctPayee] = value.toString();
} else if ((*it_split).action() == MyMoneySplit::ActionInterest) {
// put the interest in the "interest" column and convert to lowest fraction
qA[ctInterest] = value.toString();
} else if (splits.count() > 2) {
// [dv: This comment carried from the original code. I am
// not exactly clear on what it means or why we do this.]
// Put the initial pay-in nowhere (that is, ignore it). This
// is dangerous, though. The only way I can tell the initial
// pay-in apart from fees is if there are only 2 splits in
// the transaction. I wish there was a better way.
} else {
// accumulate everything else in the "fees" column
MyMoneyMoney n0 = MyMoneyMoney(qA[ctFees]);
qA[ctFees] = (n0 + value).toString();
}
// we don't add qA here for a loan transaction. we'll add one
// qA afer all of the split components have been processed.
// (see below)
}
//--- special case to hide split transaction details
else if (hide_details && (splits.count() > 2)) {
// essentially, don't add any qA entries
}
//--- default case includes all transaction details
else {
//this is when the splits are going to be shown as children of the main split
if ((splits.count() > 2) && use_summary) {
qA[ctValue].clear();
//convert to lowest fraction
qA[ctSplit] = (-(*it_split).shares() * xr).convert(fraction).toString();
qA[ctRank] = QLatin1Char('2');
} else {
//this applies when the transaction has only 2 splits, or each split is going to be
//shown separately, eg. transactions by category
switch (m_config.rowType()) {
case MyMoneyReport::eCategory:
case MyMoneyReport::eTopCategory:
if (splitAcc.isIncomeExpense())
qA[ctValue] = (-(*it_split).shares() * xr).convert(fraction).toString(); // needed for category reports, in case of multicurrency transaction it breaks it
break;
default:
break;
}
qA[ctSplit].clear();
qA[ctRank] = QLatin1Char('1');
}
qA [ctMemo] = (*it_split).memo();
if (!m_containsNonBaseCurrency && splitAcc.currencyId() != file->baseCurrency().id())
m_containsNonBaseCurrency = true;
if (report.isConvertCurrency())
qS[ctCurrency] = file->baseCurrency().id();
else
qS[ctCurrency] = splitAcc.currency().id();
if (! splitAcc.isIncomeExpense()) {
qA[ctCategory] = ((*it_split).shares().isNegative()) ?
i18n("Transfer from %1", splitAcc.fullName())
: i18n("Transfer to %1", splitAcc.fullName());
qA[ctTopCategory] = splitAcc.topParentName();
qA[ctCategoryType] = i18n("Transfer");
} else {
qA [ctCategory] = splitAcc.fullName();
qA [ctTopCategory] = splitAcc.topParentName();
qA [ctCategoryType] = KMyMoneyUtils::accountTypeToString(splitAcc.accountGroup());
}
if (use_transfers || (splitAcc.isIncomeExpense() && m_config.includes(splitAcc))) {
//if it matches the text of the main split of the transaction or
//it matches this particular split, include it
//otherwise, skip it
//if the filter is "does not contain" exclude the split if it does not match
//even it matches the whole split
if ((m_config.isInvertingText() &&
m_config.match(&(*it_split)))
|| (!m_config.isInvertingText()
&& (transaction_text
|| m_config.match(&(*it_split))))) {
if (tag_special_case) {
if (!tagIdListCache.size())
qA[ctTag] = i18n("[No Tag]");
else
for (int i = 0; i < tagIdListCache.size(); i++) {
qA[ctTag] = file->tag(tagIdListCache[i]).name().simplified();
m_rows += qA;
}
} else {
m_rows += qA;
}
}
}
}
}
if (m_config.includes(splitAcc) && use_transfers &&
!(splitAcc.isInvest() && include_me)) { // otherwise stock split is displayed twice in report
if (! splitAcc.isIncomeExpense()) {
//multiply by currency and convert to lowest fraction
qS[ctValue] = ((*it_split).shares() * xr).convert(fraction).toString();
qS[ctRank] = QLatin1Char('1');
qS[ctAccount] = splitAcc.name();
qS[ctAccountID] = splitAcc.id();
qS[ctTopAccount] = splitAcc.topParentName();
qS[ctCategory] = ((*it_split).shares().isNegative())
? i18n("Transfer to %1", a_fullname)
: i18n("Transfer from %1", a_fullname);
qS[ctInstitution] = institution.isEmpty()
? i18n("No Institution")
: file->institution(institution).name();
qS[ctMemo] = (*it_split).memo().isEmpty()
? a_memo
: (*it_split).memo();
//FIXME-ALEX When is used this? I can't find in which condition we arrive here... maybe this code is useless?
QString delimiter;
for (int i = 0; i < tagIdList.size(); i++) {
qA[ctTag] += delimiter + file->tag(tagIdList[i]).name().simplified();
delimiter = "+";
}
qS[ctPayee] = payee.isEmpty()
? qA[ctPayee]
: file->payee(payee).name().simplified();
//check the specific split against the filter for text and amount
//TODO this should be done at the engine, but I have no clear idea how -- asoliverez
//if the filter is "does not contain" exclude the split if it does not match
//even it matches the whole split
if ((m_config.isInvertingText() &&
m_config.match(&(*it_split)))
|| (!m_config.isInvertingText()
&& (transaction_text
|| m_config.match(&(*it_split))))) {
m_rows += qS;
// track accts that will need opening and closing balances
accts.insert(splitAcc.id(), splitAcc);
}
}
}
}
++it_split;
// look for wrap-around
if (it_split == splits.end())
it_split = splits.begin();
// but terminate if this transaction has only a single split
if (splits.count() < 2)
break;
//check if there have been more passes than there are splits
//this is to prevent infinite loops in cases of data inconsistency -- asoliverez
++pass;
if (pass > splits.count())
break;
} while (it_split != myBegin);
if (loan_special_case) {
m_rows += qA;
}
}
// now run through our accts list and add opening and closing balances
switch (m_config.rowType()) {
case MyMoneyReport::eAccount:
case MyMoneyReport::eTopAccount:
break;
// case MyMoneyReport::eCategory:
// case MyMoneyReport::eTopCategory:
// case MyMoneyReport::ePayee:
// case MyMoneyReport::eMonth:
// case MyMoneyReport::eWeek:
default:
return;
}
QDate startDate, endDate;
report.validDateRange(startDate, endDate);
QString strStartDate = startDate.toString(Qt::ISODate);
QString strEndDate = endDate.toString(Qt::ISODate);
startDate = startDate.addDays(-1);
QMap<QString, MyMoneyAccount>::const_iterator it_account, accts_end;
for (it_account = accts.constBegin(); it_account != accts.constEnd(); ++it_account) {
TableRow qA;
ReportAccount account = (* it_account);
//get fraction for account
int fraction = account.currency().smallestAccountFraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = file->baseCurrency().smallestAccountFraction();
QString institution = account.institutionId();
// use the institution of the parent for stock accounts
if (account.isInvest())
institution = account.parent().institutionId();
MyMoneyMoney startBalance, endBalance, startPrice, endPrice;
MyMoneyMoney startShares, endShares;
//get price and convert currency if necessary
if (m_config.isConvertCurrency()) {
startPrice = (account.deepCurrencyPrice(startDate) * account.baseCurrencyPrice(startDate)).reduce();
endPrice = (account.deepCurrencyPrice(endDate) * account.baseCurrencyPrice(endDate)).reduce();
} else {
startPrice = account.deepCurrencyPrice(startDate).reduce();
endPrice = account.deepCurrencyPrice(endDate).reduce();
}
startShares = file->balance(account.id(), startDate);
endShares = file->balance(account.id(), endDate);
//get starting and ending balances
startBalance = startShares * startPrice;
endBalance = endShares * endPrice;
//starting balance
// don't show currency if we're converting or if it's not foreign
if (!m_containsNonBaseCurrency && account.currency().id() != file->baseCurrency().id())
m_containsNonBaseCurrency = true;
if (m_config.isConvertCurrency())
qA[ctCurrency] = file->baseCurrency().id();
else
qA[ctCurrency] = account.currency().id();
qA[ctAccountID] = account.id();
qA[ctAccount] = account.name();
qA[ctTopAccount] = account.topParentName();
qA[ctInstitution] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name();
qA[ctRank] = QLatin1Char('0');
qA[ctPrice] = startPrice.convertPrecision(account.currency().pricePrecision()).toString();
if (account.isInvest()) {
qA[ctShares] = startShares.toString();
}
qA[ctPostDate] = strStartDate;
qA[ctBalance] = startBalance.convert(fraction).toString();
qA[ctValue].clear();
qA[ctID] = QLatin1Char('A');
m_rows += qA;
//ending balance
qA[ctPrice] = endPrice.convertPrecision(account.currency().pricePrecision()).toString();
if (account.isInvest()) {
qA[ctShares] = endShares.toString();
}
qA[ctPostDate] = strEndDate;
qA[ctBalance] = endBalance.toString();
qA[ctRank] = QLatin1Char('3');
qA[ctID] = QLatin1Char('Z');
m_rows += qA;
}
}
MyMoneyMoney QueryTable::helperROI(const MyMoneyMoney &buys, const MyMoneyMoney &sells, const MyMoneyMoney &startingBal, const MyMoneyMoney &endingBal, const MyMoneyMoney &cashIncome) const
{
MyMoneyMoney returnInvestment;
if (!buys.isZero() || !startingBal.isZero()) {
returnInvestment = (sells + buys + cashIncome + endingBal - startingBal) / (startingBal - buys);
returnInvestment = returnInvestment.convert(10000);
} else
returnInvestment = MyMoneyMoney(); // if no investment then no return on investment
return returnInvestment;
}
MyMoneyMoney QueryTable::helperIRR(const CashFlowList &all) const
{
MyMoneyMoney annualReturn;
try {
double irr = all.IRR();
#ifdef Q_CC_MSVC
annualReturn = MyMoneyMoney(_isnan(irr) ? 0 : irr, 10000);
#else
annualReturn = MyMoneyMoney(std::isnan(irr) ? 0 : irr, 10000);
#endif
} catch (QString e) {
qDebug() << e;
}
return annualReturn;
}
void QueryTable::sumInvestmentValues(const ReportAccount& account, QList<CashFlowList>& cfList, QList<MyMoneyMoney>& shList) const
{
for (int i = InvestmentValue::Buys; i < InvestmentValue::End; ++i)
cfList.append(CashFlowList());
for (int i = InvestmentValue::Buys; i <= InvestmentValue::BuysOfOwned; ++i)
shList.append(MyMoneyMoney());
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyReport report = m_config;
QDate startingDate;
QDate endingDate;
QDate newStartingDate;
QDate newEndingDate;
const bool isSTLT = report.isShowingSTLTCapitalGains();
const int settlementPeriod = report.settlementPeriod();
QDate termSeparator = report.termSeparator().addDays(-settlementPeriod);
report.validDateRange(startingDate, endingDate);
newStartingDate = startingDate;
newEndingDate = endingDate;
if (report.queryColumns() & MyMoneyReport::eQCcapitalgain) {
// Saturday and Sunday aren't valid settlement dates
if (endingDate.dayOfWeek() == Qt::Saturday)
endingDate = endingDate.addDays(-1);
else if (endingDate.dayOfWeek() == Qt::Sunday)
endingDate = endingDate.addDays(-2);
if (termSeparator.dayOfWeek() == Qt::Saturday)
termSeparator = termSeparator.addDays(-1);
else if (termSeparator.dayOfWeek() == Qt::Sunday)
termSeparator = termSeparator.addDays(-2);
if (startingDate.daysTo(endingDate) <= settlementPeriod) // no days to check for
return;
termSeparator = termSeparator.addDays(-settlementPeriod);
newEndingDate = endingDate.addDays(-settlementPeriod);
}
shList[BuysOfOwned] = file->balance(account.id(), newEndingDate); // get how many shares there are at the end of period
MyMoneyMoney stashedBuysOfOwned = shList.at(BuysOfOwned);
bool reportedDateRange = true; // flag marking sell transactions between startingDate and endingDate
report.setReportAllSplits(false);
report.setConsiderCategory(true);
report.clearAccountFilter();
report.addAccount(account.id());
report.setDateFilter(newStartingDate, newEndingDate);
do {
QList<MyMoneyTransaction> transactions = file->transactionList(report);
for (QList<MyMoneyTransaction>::const_reverse_iterator it_t = transactions.crbegin(); it_t != transactions.crend(); ++it_t) {
MyMoneySplit shareSplit = (*it_t).splitByAccount(account.id());
MyMoneySplit assetAccountSplit;
QList<MyMoneySplit> feeSplits;
QList<MyMoneySplit> interestSplits;
MyMoneySecurity security;
MyMoneySecurity currency;
MyMoneySplit::investTransactionTypeE transactionType;
KMyMoneyUtils::dissectTransaction((*it_t), shareSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType);
QDate postDate = (*it_t).postDate();
MyMoneyMoney price;
//get price for the day of the transaction if we have to calculate base currency
//we are using the value of the split which is in deep currency
if (m_config.isConvertCurrency())
price = account.baseCurrencyPrice(postDate); //we only need base currency because the value is in deep currency
else
price = MyMoneyMoney::ONE;
MyMoneyMoney value = assetAccountSplit.value() * price;
MyMoneyMoney shares = shareSplit.shares();
if (transactionType == MyMoneySplit::BuyShares) {
if (reportedDateRange) {
cfList[Buys].append(CashFlowListItem(postDate, value));
shList[Buys] += shares;
}
if (shList.at(BuysOfOwned).isZero()) { // add sold shares
if (shList.at(BuysOfSells) + shares > shList.at(Sells).abs()) { // add partially sold shares
MyMoneyMoney tempVal = (((shList.at(Sells).abs() - shList.at(BuysOfSells))) / shares) * value;
cfList[BuysOfSells].append(CashFlowListItem(postDate, tempVal));
shList[BuysOfSells] = shList.at(Sells).abs();
if (isSTLT && postDate < termSeparator) {
cfList[LongTermBuysOfSells].append(CashFlowListItem(postDate, tempVal));
shList[LongTermBuysOfSells] = shList.at(BuysOfSells);
}
} else { // add wholly sold shares
cfList[BuysOfSells].append(CashFlowListItem(postDate, value));
shList[BuysOfSells] += shares;
if (isSTLT && postDate < termSeparator) {
cfList[LongTermBuysOfSells].append(CashFlowListItem(postDate, value));
shList[LongTermBuysOfSells] += shares;
}
}
} else if (shList.at(BuysOfOwned) >= shares) { // substract not-sold shares
shList[BuysOfOwned] -= shares;
cfList[BuysOfOwned].append(CashFlowListItem(postDate, value));
} else { // substract partially not-sold shares
MyMoneyMoney tempVal = ((shares - shList.at(BuysOfOwned)) / shares) * value;
MyMoneyMoney tempVal2 = (shares - shList.at(BuysOfOwned));
cfList[BuysOfSells].append(CashFlowListItem(postDate, tempVal));
shList[BuysOfSells] += tempVal2;
if (isSTLT && postDate < termSeparator) {
cfList[LongTermBuysOfSells].append(CashFlowListItem(postDate, tempVal));
shList[LongTermBuysOfSells] += tempVal2;
}
cfList[BuysOfOwned].append(CashFlowListItem(postDate, (shList.at(BuysOfOwned) / shares) * value));
shList[BuysOfOwned] = MyMoneyMoney();
}
} else if (transactionType == MyMoneySplit::SellShares && reportedDateRange) {
cfList[Sells].append(CashFlowListItem(postDate, value));
shList[Sells] += shares;
} else if (transactionType == MyMoneySplit::SplitShares) { // shares variable is denominator of split ratio here
for (int i = Buys; i <= InvestmentValue::BuysOfOwned; ++i)
shList[i] /= shares;
} else if (transactionType == MyMoneySplit::AddShares || // added shares, when sold give 100% capital gain
transactionType == MyMoneySplit::ReinvestDividend) {
if (shList.at(BuysOfOwned).isZero()) { // add added/reinvested shares
if (shList.at(BuysOfSells) + shares > shList.at(Sells).abs()) { // add partially added/reinvested shares
shList[BuysOfSells] = shList.at(Sells).abs();
if (postDate < termSeparator)
shList[LongTermBuysOfSells] = shList[BuysOfSells];
} else { // add wholly added/reinvested shares
shList[BuysOfSells] += shares;
if (postDate < termSeparator)
shList[LongTermBuysOfSells] += shares;
}
} else if (shList.at(BuysOfOwned) >= shares) { // substract not-added/not-reinvested shares
shList[BuysOfOwned] -= shares;
cfList[BuysOfOwned].append(CashFlowListItem(postDate, value));
} else { // substract partially not-added/not-reinvested shares
MyMoneyMoney tempVal = (shares - shList.at(BuysOfOwned));
shList[BuysOfSells] += tempVal;
if (postDate < termSeparator)
shList[LongTermBuysOfSells] += tempVal;
cfList[BuysOfOwned].append(CashFlowListItem(postDate, (shList.at(BuysOfOwned) / shares) * value));
shList[BuysOfOwned] = MyMoneyMoney();
}
if (transactionType == MyMoneySplit::ReinvestDividend) {
value = MyMoneyMoney();
foreach (const auto split, interestSplits)
value += split.value();
value *= price;
cfList[ReinvestIncome].append(CashFlowListItem(postDate, -value));
}
} else if (transactionType == MyMoneySplit::RemoveShares && reportedDateRange) // removed shares give no value in return so no capital gain on them
shList[Sells] += shares;
else if (transactionType == MyMoneySplit::Dividend || transactionType == MyMoneySplit::Yield)
cfList[CashIncome].append(CashFlowListItem(postDate, value));
}
reportedDateRange = false;
newEndingDate = newStartingDate;
newStartingDate = newStartingDate.addYears(-1);
report.setDateFilter(newStartingDate, newEndingDate); // search for matching buy transactions year earlier
} while (
(
(report.investmentSum() == MyMoneyReport::eSumOwned && !shList[BuysOfOwned].isZero()) ||
(report.investmentSum() == MyMoneyReport::eSumSold && !shList.at(Sells).isZero() && shList.at(Sells).abs() > shList.at(BuysOfSells).abs()) ||
(report.investmentSum() == MyMoneyReport::eSumOwnedAndSold && (!shList[BuysOfOwned].isZero() || (!shList.at(Sells).isZero() && shList.at(Sells).abs() > shList.at(BuysOfSells).abs())))
) && account.openingDate() <= newEndingDate
);
// we've got buy value and no sell value of long-term shares, so get them
if (isSTLT && !shList[LongTermBuysOfSells].isZero()) {
newStartingDate = startingDate;
newEndingDate = endingDate.addDays(-settlementPeriod);
report.setDateFilter(newStartingDate, newEndingDate); // search for matching buy transactions year earlier
QList<MyMoneyTransaction> transactions = file->transactionList(report);
shList[BuysOfOwned] = shList[LongTermBuysOfSells];
foreach (const auto transaction, transactions) {
MyMoneySplit shareSplit = transaction.splitByAccount(account.id());
MyMoneySplit assetAccountSplit;
QList<MyMoneySplit> feeSplits;
QList<MyMoneySplit> interestSplits;
MyMoneySecurity security;
MyMoneySecurity currency;
MyMoneySplit::investTransactionTypeE transactionType;
KMyMoneyUtils::dissectTransaction(transaction, shareSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType);
QDate postDate = transaction.postDate();
MyMoneyMoney price;
if (m_config.isConvertCurrency())
price = account.baseCurrencyPrice(postDate); //we only need base currency because the value is in deep currency
else
price = MyMoneyMoney::ONE;
MyMoneyMoney value = assetAccountSplit.value() * price;
MyMoneyMoney shares = shareSplit.shares();
if (transactionType == MyMoneySplit::SellShares) {
if ((shList.at(LongTermSellsOfBuys) + shares).abs() >= shList.at(LongTermBuysOfSells)) { // add partially sold long-term shares
cfList[LongTermSellsOfBuys].append(CashFlowListItem(postDate, (shList.at(LongTermSellsOfBuys).abs() - shList.at(LongTermBuysOfSells)) / shares * value));
shList[LongTermSellsOfBuys] = shList.at(LongTermBuysOfSells);
break;
} else { // add wholly sold long-term shares
cfList[LongTermSellsOfBuys].append(CashFlowListItem(postDate, value));
shList[LongTermSellsOfBuys] += shares;
}
} else if (transactionType == MyMoneySplit::RemoveShares) {
if ((shList.at(LongTermSellsOfBuys) + shares).abs() >= shList.at(LongTermBuysOfSells)) {
shList[LongTermSellsOfBuys] = shList.at(LongTermBuysOfSells);
break;
} else
shList[LongTermSellsOfBuys] += shares;
}
}
}
shList[BuysOfOwned] = stashedBuysOfOwned;
report.setDateFilter(startingDate, endingDate); // reset data filter for next security
return;
}
void QueryTable::constructPerformanceRow(const ReportAccount& account, TableRow& result, CashFlowList &all) const
{
MyMoneyReport report = m_config;
QDate startingDate;
QDate endingDate;
report.validDateRange(startingDate, endingDate);
startingDate = startingDate.addDays(-1);
MyMoneyFile* file = MyMoneyFile::instance();
//get fraction depending on type of account
int fraction = account.currency().smallestAccountFraction();
MyMoneyMoney price;
if (m_config.isConvertCurrency())
price = account.deepCurrencyPrice(startingDate) * account.baseCurrencyPrice(startingDate);
else
price = account.deepCurrencyPrice(startingDate);
MyMoneyMoney startingBal = file->balance(account.id(), startingDate) * price;
//convert to lowest fraction
startingBal = startingBal.convert(fraction);
//calculate ending balance
if (m_config.isConvertCurrency())
price = account.deepCurrencyPrice(endingDate) * account.baseCurrencyPrice(endingDate);
else
price = account.deepCurrencyPrice(endingDate);
MyMoneyMoney endingBal = file->balance((account).id(), endingDate) * price;
//convert to lowest fraction
endingBal = endingBal.convert(fraction);
QList<CashFlowList> cfList;
QList<MyMoneyMoney> shList;
sumInvestmentValues(account, cfList, shList);
MyMoneyMoney buysTotal;
MyMoneyMoney sellsTotal;
MyMoneyMoney cashIncomeTotal;
MyMoneyMoney reinvestIncomeTotal;
switch (m_config.investmentSum()) {
case MyMoneyReport::eSumOwnedAndSold:
buysTotal = cfList.at(BuysOfSells).total() + cfList.at(BuysOfOwned).total();
sellsTotal = cfList.at(Sells).total();
cashIncomeTotal = cfList.at(CashIncome).total();
reinvestIncomeTotal = cfList.at(ReinvestIncome).total();
startingBal = MyMoneyMoney();
if (buysTotal.isZero() && sellsTotal.isZero() &&
cashIncomeTotal.isZero() && reinvestIncomeTotal.isZero())
return;
all.append(cfList.at(BuysOfSells));
all.append(cfList.at(BuysOfOwned));
all.append(cfList.at(Sells));
all.append(cfList.at(CashIncome));
result[ctSells] = sellsTotal.toString();
result[ctCashIncome] = cashIncomeTotal.toString();
result[ctReinvestIncome] = reinvestIncomeTotal.toString();
result[ctEndingBalance] = endingBal.toString();
break;
case MyMoneyReport::eSumOwned:
buysTotal = cfList.at(BuysOfOwned).total();
startingBal = MyMoneyMoney();
if (buysTotal.isZero() && endingBal.isZero())
return;
all.append(cfList.at(BuysOfOwned));
all.append(CashFlowListItem(endingDate, endingBal));
result[ctReinvestIncome] = reinvestIncomeTotal.toString();
result[ctMarketValue] = endingBal.toString();
break;
case MyMoneyReport::eSumSold:
buysTotal = cfList.at(BuysOfSells).total();
sellsTotal = cfList.at(Sells).total();
cashIncomeTotal = cfList.at(CashIncome).total();
startingBal = endingBal = MyMoneyMoney();
// check if there are any meaningfull values before adding them to results
if (buysTotal.isZero() && sellsTotal.isZero() && cashIncomeTotal.isZero())
return;
all.append(cfList.at(BuysOfSells));
all.append(cfList.at(Sells));
all.append(cfList.at(CashIncome));
result[ctSells] = sellsTotal.toString();
result[ctCashIncome] = cashIncomeTotal.toString();
break;
case MyMoneyReport::eSumPeriod:
default:
buysTotal = cfList.at(Buys).total();
sellsTotal = cfList.at(Sells).total();
cashIncomeTotal = cfList.at(CashIncome).total();
reinvestIncomeTotal = cfList.at(ReinvestIncome).total();
if (buysTotal.isZero() && sellsTotal.isZero() &&
cashIncomeTotal.isZero() && reinvestIncomeTotal.isZero() &&
startingBal.isZero() && endingBal.isZero())
return;
all.append(cfList.at(Buys));
all.append(cfList.at(Sells));
all.append(cfList.at(CashIncome));
all.append(CashFlowListItem(startingDate, -startingBal));
all.append(CashFlowListItem(endingDate, endingBal));
result[ctSells] = sellsTotal.toString();
result[ctCashIncome] = cashIncomeTotal.toString();
result[ctReinvestIncome] = reinvestIncomeTotal.toString();
result[ctStartingBalance] = startingBal.toString();
result[ctEndingBalance] = endingBal.toString();
break;
}
MyMoneyMoney returnInvestment = helperROI(buysTotal - reinvestIncomeTotal, sellsTotal, startingBal, endingBal, cashIncomeTotal);
MyMoneyMoney annualReturn = helperIRR(all);
result[ctBuys] = buysTotal.toString();
result[ctReturn] = annualReturn.toString();
result[ctReturnInvestment] = returnInvestment.toString();
result[ctEquityType] = MyMoneySecurity::securityTypeToString(file->security(account.currencyId()).securityType());
}
void QueryTable::constructCapitalGainRow(const ReportAccount& account, TableRow& result) const
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<CashFlowList> cfList;
QList<MyMoneyMoney> shList;
sumInvestmentValues(account, cfList, shList);
MyMoneyMoney buysTotal = cfList.at(BuysOfSells).total();
MyMoneyMoney sellsTotal = cfList.at(Sells).total();
MyMoneyMoney longTermBuysOfSellsTotal = cfList.at(LongTermBuysOfSells).total();
MyMoneyMoney longTermSellsOfBuys = cfList.at(LongTermSellsOfBuys).total();
switch (m_config.investmentSum()) {
case MyMoneyReport::eSumOwned:
{
if (shList.at(BuysOfOwned).isZero())
return;
MyMoneyReport report = m_config;
QDate startingDate;
QDate endingDate;
report.validDateRange(startingDate, endingDate);
//get fraction depending on type of account
int fraction = account.currency().smallestAccountFraction();
MyMoneyMoney price;
//calculate ending balance
if (m_config.isConvertCurrency())
price = account.deepCurrencyPrice(endingDate) * account.baseCurrencyPrice(endingDate);
else
price = account.deepCurrencyPrice(endingDate);
MyMoneyMoney endingBal = shList.at(BuysOfOwned) * price;
//convert to lowest fraction
endingBal = endingBal.convert(fraction);
buysTotal = cfList.at(BuysOfOwned).total() - cfList.at(ReinvestIncome).total();
int pricePrecision = file->security(account.currencyId()).pricePrecision();
result[ctBuys] = buysTotal.toString();
result[ctShares] = shList.at(BuysOfOwned).toString();
result[ctBuyPrice] = (buysTotal.abs() / shList.at(BuysOfOwned)).convertPrecision(pricePrecision).toString();
result[ctLastPrice] = price.toString();
result[ctMarketValue] = endingBal.toString();
result[ctCapitalGain] = (buysTotal + endingBal).toString();
result[ctPercentageGain] = ((buysTotal + endingBal)/buysTotal.abs()).toString();
break;
}
case MyMoneyReport::eSumSold:
default:
buysTotal = cfList.at(BuysOfSells).total() - cfList.at(ReinvestIncome).total();
sellsTotal = cfList.at(Sells).total();
longTermBuysOfSellsTotal = cfList.at(LongTermBuysOfSells).total();
longTermSellsOfBuys = cfList.at(LongTermSellsOfBuys).total();
// check if there are any meaningfull values before adding them to results
if (buysTotal.isZero() && sellsTotal.isZero() &&
longTermBuysOfSellsTotal.isZero() && longTermSellsOfBuys.isZero())
return;
result[ctBuys] = buysTotal.toString();
result[ctSells] = sellsTotal.toString();
result[ctCapitalGain] = (buysTotal + sellsTotal).toString();
if (m_config.isShowingSTLTCapitalGains()) {
result[ctBuysLT] = longTermBuysOfSellsTotal.toString();
result[ctSellsLT] = longTermSellsOfBuys.toString();
result[ctCapitalGainLT] = (longTermBuysOfSellsTotal + longTermSellsOfBuys).toString();
result[ctBuysST] = (buysTotal - longTermBuysOfSellsTotal).toString();
result[ctSellsST] = (sellsTotal - longTermSellsOfBuys).toString();
result[ctCapitalGainST] = ((buysTotal - longTermBuysOfSellsTotal) + (sellsTotal - longTermSellsOfBuys)).toString();
}
break;
}
result[ctEquityType] = MyMoneySecurity::securityTypeToString(file->security(account.currencyId()).securityType());
}
void QueryTable::constructAccountTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
//make sure we have all subaccounts of investment accounts
includeInvestmentSubAccounts();
QMap<QString, QMap<QString, CashFlowList>> currencyCashFlow; // for total calculation
QList<MyMoneyAccount> accounts;
file->accountList(accounts);
for (auto it_account = accounts.constBegin(); it_account != accounts.constEnd(); ++it_account) {
// Note, "Investment" accounts are never included in account rows because
// they don't contain anything by themselves. In reports, they are only
// useful as a "topaccount" aggregator of stock accounts
- if ((*it_account).isAssetLiability() && m_config.includes((*it_account)) && (*it_account).accountType() != MyMoneyAccount::Investment) {
+ if ((*it_account).isAssetLiability() && m_config.includes((*it_account)) && (*it_account).accountType() != eMyMoney::Account::Investment) {
// don't add the account if it is closed. In fact, the business logic
// should prevent that an account can be closed with a balance not equal
// to zero, but we never know.
MyMoneyMoney shares = file->balance((*it_account).id(), m_config.toDate());
if (shares.isZero() && (*it_account).isClosed())
continue;
ReportAccount account(*it_account);
TableRow qaccountrow;
CashFlowList accountCashflow; // for total calculation
switch(m_config.queryColumns()) {
case MyMoneyReport::eQCperformance:
{
constructPerformanceRow(account, qaccountrow, accountCashflow);
if (!qaccountrow.isEmpty()) {
// assuming that that report is grouped by topaccount
qaccountrow[ctTopAccount] = account.topParentName();
if (!m_containsNonBaseCurrency && account.currency().id() != file->baseCurrency().id())
m_containsNonBaseCurrency = true;
if (m_config.isConvertCurrency())
qaccountrow[ctCurrency] = file->baseCurrency().id();
else
qaccountrow[ctCurrency] = account.currency().id();
if (!currencyCashFlow.value(qaccountrow.value(ctCurrency)).contains(qaccountrow.value(ctTopAccount)))
currencyCashFlow[qaccountrow.value(ctCurrency)].insert(qaccountrow.value(ctTopAccount), accountCashflow); // create cashflow for unknown account...
else
currencyCashFlow[qaccountrow.value(ctCurrency)][qaccountrow.value(ctTopAccount)] += accountCashflow; // ...or add cashflow for known account
}
break;
}
case MyMoneyReport::eQCcapitalgain:
constructCapitalGainRow(account, qaccountrow);
break;
default:
{
//get fraction for account
int fraction = account.currency().smallestAccountFraction() != -1 ?
account.currency().smallestAccountFraction() : file->baseCurrency().smallestAccountFraction();
MyMoneyMoney netprice = account.deepCurrencyPrice(m_config.toDate());
if (m_config.isConvertCurrency() && account.isForeignCurrency())
netprice *= account.baseCurrencyPrice(m_config.toDate()); // display currency is base currency, so set the price
netprice = netprice.reduce();
shares = shares.reduce();
int pricePrecision = file->security(account.currencyId()).pricePrecision();
qaccountrow[ctPrice] = netprice.convertPrecision(pricePrecision).toString();
qaccountrow[ctValue] = (netprice * shares).convert(fraction).toString();
qaccountrow[ctShares] = shares.toString();
QString iid = account.institutionId();
// If an account does not have an institution, get it from the top-parent.
if (iid.isEmpty() && !account.isTopLevel())
iid = account.topParent().institutionId();
if (iid.isEmpty())
qaccountrow[ctInstitution] = i18nc("No institution", "None");
else
qaccountrow[ctInstitution] = file->institution(iid).name();
qaccountrow[ctType] = KMyMoneyUtils::accountTypeToString(account.accountType());
}
}
if (qaccountrow.isEmpty()) // don't add the account if there are no calculated values
continue;
qaccountrow[ctRank] = QLatin1Char('1');
qaccountrow[ctAccount] = account.name();
qaccountrow[ctAccountID] = account.id();
qaccountrow[ctTopAccount] = account.topParentName();
if (!m_containsNonBaseCurrency && account.currency().id() != file->baseCurrency().id())
m_containsNonBaseCurrency = true;
if (m_config.isConvertCurrency())
qaccountrow[ctCurrency] = file->baseCurrency().id();
else
qaccountrow[ctCurrency] = account.currency().id();
m_rows.append(qaccountrow);
}
}
if (m_config.queryColumns() == MyMoneyReport::eQCperformance && m_config.isShowingColumnTotals()) {
TableRow qtotalsrow;
qtotalsrow[ctRank] = QLatin1Char('4'); // add identification of row as total
QMap<QString, CashFlowList> currencyGrandCashFlow;
QMap<QString, QMap<QString, CashFlowList>>::iterator currencyAccGrp = currencyCashFlow.begin();
while (currencyAccGrp != currencyCashFlow.end()) {
// convert map of top accounts with cashflows to TableRow
for (QMap<QString, CashFlowList>::iterator topAccount = (*currencyAccGrp).begin(); topAccount != (*currencyAccGrp).end(); ++topAccount) {
qtotalsrow[ctTopAccount] = topAccount.key();
qtotalsrow[ctReturn] = helperIRR(topAccount.value()).toString();
qtotalsrow[ctCurrency] = currencyAccGrp.key();
currencyGrandCashFlow[currencyAccGrp.key()] += topAccount.value(); // cumulative sum of cashflows of each topaccount
m_rows.append(qtotalsrow); // rows aren't sorted yet, so no problem with adding them randomly at the end
}
++currencyAccGrp;
}
QMap<QString, CashFlowList>::iterator currencyGrp = currencyGrandCashFlow.begin();
qtotalsrow[ctTopAccount].clear(); // empty topaccount because it's grand cashflow
while (currencyGrp != currencyGrandCashFlow.end()) {
qtotalsrow[ctReturn] = helperIRR(currencyGrp.value()).toString();
qtotalsrow[ctCurrency] = currencyGrp.key();
m_rows.append(qtotalsrow);
++currencyGrp;
}
}
}
void QueryTable::constructSplitsTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
//make sure we have all subaccounts of investment accounts
includeInvestmentSubAccounts();
MyMoneyReport report(m_config);
report.setReportAllSplits(false);
report.setConsiderCategory(true);
// support for opening and closing balances
QMap<QString, MyMoneyAccount> accts;
//get all transactions for this report
QList<MyMoneyTransaction> transactions = file->transactionList(report);
for (QList<MyMoneyTransaction>::const_iterator it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) {
TableRow qA, qS;
QDate pd;
qA[ctID] = qS[ctID] = (* it_transaction).id();
qA[ctEntryDate] = qS[ctEntryDate] = (* it_transaction).entryDate().toString(Qt::ISODate);
qA[ctPostDate] = qS[ctPostDate] = (* it_transaction).postDate().toString(Qt::ISODate);
qA[ctCommodity] = qS[ctCommodity] = (* it_transaction).commodity();
pd = (* it_transaction).postDate();
qA[ctMonth] = qS[ctMonth] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate));
qA[ctWeek] = qS[ctWeek] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate));
if (!m_containsNonBaseCurrency && (*it_transaction).commodity() != file->baseCurrency().id())
m_containsNonBaseCurrency = true;
if (report.isConvertCurrency())
qA[ctCurrency] = qS[ctCurrency] = file->baseCurrency().id();
else
qA[ctCurrency] = qS[ctCurrency] = (*it_transaction).commodity();
// to handle splits, we decide on which account to base the split
// (a reference point or point of view so to speak). here we take the
// first account that is a stock account or loan account (or the first account
// that is not an income or expense account if there is no stock or loan account)
// to be the account (qA) that will have the sub-item "split" entries. we add
// one transaction entry (qS) for each subsequent entry in the split.
const QList<MyMoneySplit>& splits = (*it_transaction).splits();
QList<MyMoneySplit>::const_iterator myBegin, it_split;
//S_end = splits.end();
for (it_split = splits.constBegin(), myBegin = splits.constEnd(); it_split != splits.constEnd(); ++it_split) {
ReportAccount splitAcc = (* it_split).accountId();
// always put split with a "stock" account if it exists
if (splitAcc.isInvest())
break;
// prefer to put splits with a "loan" account if it exists
if (splitAcc.isLoan())
myBegin = it_split;
if ((myBegin == splits.end()) && ! splitAcc.isIncomeExpense()) {
myBegin = it_split;
}
}
// select our "reference" split
if (it_split == splits.end()) {
it_split = myBegin;
} else {
myBegin = it_split;
}
// if the split is still unknown, use the first one. I have seen this
// happen with a transaction that has only a single split referencing an income or expense
// account and has an amount and value of 0. Such a transaction will fall through
// the above logic and leave 'it_split' pointing to splits.end() which causes the remainder
// of this to end in an infinite loop.
if (it_split == splits.end()) {
it_split = splits.begin();
}
// for "loan" reports, the loan transaction gets special treatment.
// the splits of a loan transaction are placed on one line in the
// reference (loan) account (qA). however, we process the matching
// split entries (qS) normally.
bool loan_special_case = false;
if (m_config.queryColumns() & MyMoneyReport::eQCloan) {
ReportAccount splitAcc = (*it_split).accountId();
loan_special_case = splitAcc.isLoan();
}
// There is a slight chance that at this point myBegin is still pointing to splits.end() if the
// transaction only has income and expense splits (which should not happen). In that case, point
// it to the first split
if (myBegin == splits.end()) {
myBegin = splits.begin();
}
//the account of the beginning splits
ReportAccount myBeginAcc = (*myBegin).accountId();
bool include_me = true;
QString a_fullname;
QString a_memo;
int pass = 1;
do {
MyMoneyMoney xr;
ReportAccount splitAcc = (* it_split).accountId();
//get fraction for account
int fraction = splitAcc.currency().smallestAccountFraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = file->baseCurrency().smallestAccountFraction();
QString institution = splitAcc.institutionId();
QString payee = (*it_split).payeeId();
const QList<QString> tagIdList = (*it_split).tagIdList();
if (m_config.isConvertCurrency()) {
xr = (splitAcc.deepCurrencyPrice((*it_transaction).postDate()) * splitAcc.baseCurrencyPrice((*it_transaction).postDate())).reduce();
} else {
xr = splitAcc.deepCurrencyPrice((*it_transaction).postDate()).reduce();
}
// reverse the sign of incomes and expenses to keep consistency in the way it is displayed in other reports
if (splitAcc.isIncomeExpense()) {
xr = -xr;
}
if (splitAcc.isInvest()) {
// use the institution of the parent for stock accounts
institution = splitAcc.parent().institutionId();
MyMoneyMoney shares = (*it_split).shares();
int pricePrecision = file->security(splitAcc.currencyId()).pricePrecision();
qA[ctAction] = (*it_split).action();
qA[ctShares] = shares.isZero() ? QString() : (*it_split).shares().toString();
qA[ctPrice] = shares.isZero() ? QString() : xr.convertPrecision(pricePrecision).toString();
if (((*it_split).action() == MyMoneySplit::ActionBuyShares) && (*it_split).shares().isNegative())
qA[ctAction] = "Sell";
qA[ctInvestAccount] = splitAcc.parent().name();
}
include_me = m_config.includes(splitAcc);
a_fullname = splitAcc.fullName();
a_memo = (*it_split).memo();
int pricePrecision = file->security(splitAcc.currencyId()).pricePrecision();
qA[ctPrice] = xr.convertPrecision(pricePrecision).toString();
qA[ctAccount] = splitAcc.name();
qA[ctAccountID] = splitAcc.id();
qA[ctTopAccount] = splitAcc.topParentName();
qA[ctInstitution] = institution.isEmpty()
? i18n("No Institution")
: file->institution(institution).name();
//FIXME-ALEX Is this useless? Isn't constructSplitsTable called only for cashflow type report?
QString delimiter;
foreach(const auto tagId, tagIdList) {
qA[ctTag] += delimiter + file->tag(tagId).name().simplified();
delimiter = QLatin1Char(',');
}
qA[ctPayee] = payee.isEmpty()
? i18n("[Empty Payee]")
: file->payee(payee).name().simplified();
qA[ctReconcileDate] = (*it_split).reconcileDate().toString(Qt::ISODate);
qA[ctReconcileFlag] = KMyMoneyUtils::reconcileStateToString((*it_split).reconcileFlag(), true);
qA[ctNumber] = (*it_split).number();
qA[ctMemo] = a_memo;
qS[ctReconcileDate] = qA[ctReconcileDate];
qS[ctReconcileFlag] = qA[ctReconcileFlag];
qS[ctNumber] = qA[ctNumber];
qS[ctTopCategory] = splitAcc.topParentName();
// only include the configured accounts
if (include_me) {
// add the "summarized" split transaction
// this is the sub-total of the split detail
// convert to lowest fraction
qA[ctValue] = ((*it_split).shares() * xr).convert(fraction).toString();
qA[ctRank] = QLatin1Char('1');
//fill in account information
if (! splitAcc.isIncomeExpense() && it_split != myBegin) {
qA[ctAccount] = ((*it_split).shares().isNegative()) ?
i18n("Transfer to %1", myBeginAcc.fullName())
: i18n("Transfer from %1", myBeginAcc.fullName());
} else if (it_split == myBegin) {
//handle the main split
if ((splits.count() > 2)) {
//if it is the main split and has multiple splits, note that
qA[ctAccount] = i18n("[Split Transaction]");
} else {
//fill the account name of the second split
QList<MyMoneySplit>::const_iterator tempSplit = splits.constBegin();
//there are supposed to be only 2 splits if we ever get here
if (tempSplit == myBegin && splits.count() > 1)
++tempSplit;
//show the name of the category, or "transfer to/from" if it as an account
ReportAccount tempSplitAcc = (*tempSplit).accountId();
if (! tempSplitAcc.isIncomeExpense()) {
qA[ctAccount] = ((*it_split).shares().isNegative()) ?
i18n("Transfer to %1", tempSplitAcc.fullName())
: i18n("Transfer from %1", tempSplitAcc.fullName());
} else {
qA[ctAccount] = tempSplitAcc.fullName();
}
}
} else {
//in any other case, fill in the account name of the main split
qA[ctAccount] = myBeginAcc.fullName();
}
//category data is always the one of the split
qA [ctCategory] = splitAcc.fullName();
qA [ctTopCategory] = splitAcc.topParentName();
qA [ctCategoryType] = KMyMoneyUtils::accountTypeToString(splitAcc.accountGroup());
m_rows += qA;
// track accts that will need opening and closing balances
accts.insert(splitAcc.id(), splitAcc);
}
++it_split;
// look for wrap-around
if (it_split == splits.end())
it_split = splits.begin();
//check if there have been more passes than there are splits
//this is to prevent infinite loops in cases of data inconsistency -- asoliverez
++pass;
if (pass > splits.count())
break;
} while (it_split != myBegin);
if (loan_special_case) {
m_rows += qA;
}
}
// now run through our accts list and add opening and closing balances
switch (m_config.rowType()) {
case MyMoneyReport::eAccount:
case MyMoneyReport::eTopAccount:
break;
// case MyMoneyReport::eCategory:
// case MyMoneyReport::eTopCategory:
// case MyMoneyReport::ePayee:
// case MyMoneyReport::eMonth:
// case MyMoneyReport::eWeek:
default:
return;
}
QDate startDate, endDate;
report.validDateRange(startDate, endDate);
QString strStartDate = startDate.toString(Qt::ISODate);
QString strEndDate = endDate.toString(Qt::ISODate);
startDate = startDate.addDays(-1);
QMap<QString, MyMoneyAccount>::const_iterator it_account, accts_end;
for (it_account = accts.constBegin(); it_account != accts.constEnd(); ++it_account) {
TableRow qA;
ReportAccount account = (* it_account);
//get fraction for account
int fraction = account.currency().smallestAccountFraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = file->baseCurrency().smallestAccountFraction();
QString institution = account.institutionId();
// use the institution of the parent for stock accounts
if (account.isInvest())
institution = account.parent().institutionId();
MyMoneyMoney startBalance, endBalance, startPrice, endPrice;
MyMoneyMoney startShares, endShares;
//get price and convert currency if necessary
if (m_config.isConvertCurrency()) {
startPrice = (account.deepCurrencyPrice(startDate) * account.baseCurrencyPrice(startDate)).reduce();
endPrice = (account.deepCurrencyPrice(endDate) * account.baseCurrencyPrice(endDate)).reduce();
} else {
startPrice = account.deepCurrencyPrice(startDate).reduce();
endPrice = account.deepCurrencyPrice(endDate).reduce();
}
startShares = file->balance(account.id(), startDate);
endShares = file->balance(account.id(), endDate);
//get starting and ending balances
startBalance = startShares * startPrice;
endBalance = endShares * endPrice;
//starting balance
// don't show currency if we're converting or if it's not foreign
if (!m_containsNonBaseCurrency && account.currency().id() != file->baseCurrency().id())
m_containsNonBaseCurrency = true;
if (m_config.isConvertCurrency())
qA[ctCurrency] = file->baseCurrency().id();
else
qA[ctCurrency] = account.currency().id();
qA[ctAccountID] = account.id();
qA[ctAccount] = account.name();
qA[ctTopAccount] = account.topParentName();
qA[ctInstitution] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name();
qA[ctRank] = QLatin1Char('0');
int pricePrecision = file->security(account.currencyId()).pricePrecision();
qA[ctPrice] = startPrice.convertPrecision(pricePrecision).toString();
if (account.isInvest()) {
qA[ctShares] = startShares.toString();
}
qA[ctPostDate] = strStartDate;
qA[ctBalance] = startBalance.convert(fraction).toString();
qA[ctValue].clear();
qA[ctID] = QLatin1Char('A');
m_rows += qA;
qA[ctRank] = QLatin1Char('3');
//ending balance
qA[ctPrice] = endPrice.convertPrecision(pricePrecision).toString();
if (account.isInvest()) {
qA[ctShares] = endShares.toString();
}
qA[ctPostDate] = strEndDate;
qA[ctBalance] = endBalance.toString();
qA[ctID] = QLatin1Char('Z');
m_rows += qA;
}
}
}
diff --git a/kmymoney/reports/reportaccount.cpp b/kmymoney/reports/reportaccount.cpp
index 082ebbaa1..607c99c60 100644
--- a/kmymoney/reports/reportaccount.cpp
+++ b/kmymoney/reports/reportaccount.cpp
@@ -1,332 +1,333 @@
/***************************************************************************
reportaccount.cpp
-------------------
begin : Mon May 17 2004
copyright : (C) 2004-2005 by Ace Jones
email : <ace.j@hotpop.com>
Thomas Baumgart <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 "reportaccount.h"
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
// This is just needed for i18n(). Once I figure out how to handle i18n
// without using this macro directly, I'll be freed of KDE dependency. This
// is a minor problem because we use these terms when rendering to HTML,
// and a more major problem because we need it to translate account types
-// (e.g. MyMoneyAccount::Checkings) into their text representation. We also
+// (e.g. eMyMoney::Account::Checkings) into their text representation. We also
// use that text representation in the core data structure of the report. (Ace)
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneyprice.h"
#include "mymoneysecurity.h"
#include "reportdebug.h"
namespace reports
{
ReportAccount::ReportAccount()
{
}
ReportAccount::ReportAccount(const ReportAccount& copy):
MyMoneyAccount(copy), m_nameHierarchy(copy.m_nameHierarchy)
{
// NOTE: I implemented the copy constructor solely for debugging reasons
DEBUG_ENTER(Q_FUNC_INFO);
}
ReportAccount::ReportAccount(const QString& accountid):
MyMoneyAccount(MyMoneyFile::instance()->account(accountid))
{
DEBUG_ENTER(Q_FUNC_INFO);
DEBUG_OUTPUT(QString("Account %1").arg(accountid));
calculateAccountHierarchy();
}
ReportAccount::ReportAccount(const MyMoneyAccount& account):
MyMoneyAccount(account)
{
DEBUG_ENTER(Q_FUNC_INFO);
DEBUG_OUTPUT(QString("Account %1").arg(account.id()));
calculateAccountHierarchy();
}
void ReportAccount::calculateAccountHierarchy()
{
DEBUG_ENTER(Q_FUNC_INFO);
MyMoneyFile* file = MyMoneyFile::instance();
QString resultid = id();
QString parentid = parentAccountId();
#ifdef DEBUG_HIDE_SENSITIVE
m_nameHierarchy.prepend(file->account(resultid).id());
#else
m_nameHierarchy.prepend(file->account(resultid).name());
#endif
while (!parentid.isEmpty() && !file->isStandardAccount(parentid)) {
// take on the identity of our parent
resultid = parentid;
// and try again
parentid = file->account(resultid).parentAccountId();
#ifdef DEBUG_HIDE_SENSITIVE
m_nameHierarchy.prepend(file->account(resultid).id());
#else
m_nameHierarchy.prepend(file->account(resultid).name());
#endif
}
}
MyMoneyMoney ReportAccount::deepCurrencyPrice(const QDate& date, bool exactDate) const
{
DEBUG_ENTER(Q_FUNC_INFO);
MyMoneyMoney result(1, 1);
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneySecurity undersecurity = file->security(currencyId());
if (! undersecurity.isCurrency()) {
const MyMoneyPrice &price = file->price(undersecurity.id(), undersecurity.tradingCurrency(), date, exactDate);
if (price.isValid()) {
result = price.rate(undersecurity.tradingCurrency());
DEBUG_OUTPUT(QString("Converting under %1 to deep %2, price on %3 is %4")
.arg(undersecurity.name())
.arg(file->security(undersecurity.tradingCurrency()).name())
.arg(date.toString())
.arg(result.toDouble()));
} else {
DEBUG_OUTPUT(QString("No price to convert under %1 to deep %2 on %3")
.arg(undersecurity.name())
.arg(file->security(undersecurity.tradingCurrency()).name())
.arg(date.toString()));
result = MyMoneyMoney();
}
}
return result;
}
MyMoneyMoney ReportAccount::baseCurrencyPrice(const QDate& date, bool exactDate) const
{
// Note that whether or not the user chooses to convert to base currency, all the values
// for a given account/category are converted to the currency for THAT account/category
// The "Convert to base currency" tells the report to convert from the account/category
// currency to the file's base currency.
//
// An example where this matters is if Category 'C' and account 'U' are in USD, but
// Account 'J' is in JPY. Say there are two transactions, one is US$100 from U to C,
// the other is JPY10,000 from J to C. Given a JPY price of USD$0.01, this means
// C will show a balance of $200 NO MATTER WHAT the user chooses for 'convert to base
// currency. This confused me for a while, which is why I wrote this comment.
// --acejones
DEBUG_ENTER(Q_FUNC_INFO);
MyMoneyMoney result(1, 1);
MyMoneyFile* file = MyMoneyFile::instance();
if (isForeignCurrency()) {
result = foreignCurrencyPrice(file->baseCurrency().id(), date, exactDate);
}
return result;
}
MyMoneyMoney ReportAccount::foreignCurrencyPrice(const QString foreignCurrency, const QDate& date, bool exactDate) const
{
DEBUG_ENTER(Q_FUNC_INFO);
MyMoneyMoney result(1, 1);
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneySecurity security = file->security(foreignCurrency);
//check whether it is a currency or a commodity. In the latter case case, get the trading currency
QString tradingCurrency;
if (security.isCurrency()) {
tradingCurrency = foreignCurrency;
} else {
tradingCurrency = security.tradingCurrency();
}
//It makes no sense to get the price if both currencies are the same
if (currency().id() != tradingCurrency) {
const MyMoneyPrice &price = file->price(currency().id(), tradingCurrency, date, exactDate);
if (price.isValid()) {
result = price.rate(tradingCurrency);
DEBUG_OUTPUT(QString("Converting deep %1 to currency %2, price on %3 is %4")
.arg(file->currency(currency().id()).name())
.arg(file->currency(foreignCurrency).name())
.arg(date.toString())
.arg(result.toDouble()));
} else {
DEBUG_OUTPUT(QString("No price to convert deep %1 to currency %2 on %3")
.arg(file->currency(currency().id()).name())
.arg(file->currency(foreignCurrency).name())
.arg(date.toString()));
}
}
return result;
}
/**
* Fetch the trading currency of this account's currency
*
* @return The account's currency trading currency
*/
MyMoneySecurity ReportAccount::currency() const
{
MyMoneyFile* file = MyMoneyFile::instance();
// First, get the deep currency
MyMoneySecurity deepcurrency = file->security(currencyId());
if (! deepcurrency.isCurrency())
deepcurrency = file->security(deepcurrency.tradingCurrency());
// Return the deep currency's ID
return deepcurrency;
}
/**
* Determine if this account's deep currency is different from the file's
* base currency
*
* @return bool True if this account is in a foreign currency
*/
bool ReportAccount::isForeignCurrency() const
{
return (currency().id() != MyMoneyFile::instance()->baseCurrency().id());
}
bool ReportAccount::operator<(const ReportAccount& second) const
{
// DEBUG_ENTER(Q_FUNC_INFO);
bool result = false;
bool haveresult = false;
QStringList::const_iterator it_first = m_nameHierarchy.begin();
QStringList::const_iterator it_second = second.m_nameHierarchy.begin();
while (it_first != m_nameHierarchy.end()) {
// The first string is longer than the second, but otherwise identical
if (it_second == second.m_nameHierarchy.end()) {
result = false;
haveresult = true;
break;
}
if ((*it_first) < (*it_second)) {
result = true;
haveresult = true;
break;
} else if ((*it_first) > (*it_second)) {
result = false;
haveresult = true;
break;
}
++it_first;
++it_second;
}
// The second string is longer than the first, but otherwise identical
if (!haveresult && (it_second != second.m_nameHierarchy.end()))
result = true;
// DEBUG_OUTPUT(QString("%1 < %2 is %3").arg(debugName(),second.debugName()).arg(result));
return result;
}
/**
* The name of only this account. No matter how deep the hierarchy, this
* method only returns the last name in the list, which is the engine name]
* of this account.
*
* @return QString The account's name
*/
QString ReportAccount::name() const
{
return m_nameHierarchy.back();
}
// MyMoneyAccount:fullHierarchyDebug()
QString ReportAccount::debugName() const
{
return m_nameHierarchy.join("|");
}
// MyMoneyAccount:fullHierarchy()
QString ReportAccount::fullName() const
{
return m_nameHierarchy.join(": ");
}
// MyMoneyAccount:isTopCategory()
bool ReportAccount::isTopLevel() const
{
return (m_nameHierarchy.size() == 1);
}
// MyMoneyAccount:hierarchyDepth()
unsigned ReportAccount::hierarchyDepth() const
{
return (m_nameHierarchy.size());
}
ReportAccount ReportAccount::parent() const
{
return ReportAccount(parentAccountId());
}
ReportAccount ReportAccount::topParent() const
{
DEBUG_ENTER(Q_FUNC_INFO);
MyMoneyFile* file = MyMoneyFile::instance();
QString resultid = id();
QString parentid = parentAccountId();
while (!parentid.isEmpty() && !file->isStandardAccount(parentid)) {
// take on the identity of our parent
resultid = parentid;
// and try again
parentid = file->account(resultid).parentAccountId();
}
return ReportAccount(resultid);
}
QString ReportAccount::topParentName() const
{
return m_nameHierarchy.first();
}
bool ReportAccount::isLiquidLiability() const
{
- return accountType() == MyMoneyAccount::CreditCard;
+ return accountType() == eMyMoney::Account::CreditCard;
}
} // end namespace reports
diff --git a/kmymoney/reports/reporttable.h b/kmymoney/reports/reporttable.h
index 1dd8d8374..3e20d16f6 100644
--- a/kmymoney/reports/reporttable.h
+++ b/kmymoney/reports/reporttable.h
@@ -1,149 +1,151 @@
/***************************************************************************
reporttable.h
-------------------
begin : Mon May 7 2007
copyright : (C) 2007 Thomas Baumgart
email : Thomas Baumgart <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 REPORTTABLE_H
#define REPORTTABLE_H
// ----------------------------------------------------------------------------
// QT Includes
+#include <QObject>
+
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyreport.h"
namespace reports
{
class KReportChartView;
/**
* This class serves as base class definition for the concrete report classes
* This class is abstract but it contains common code used by all children classes
*/
class ReportTable : public QObject
{
Q_OBJECT
private:
/**
* Tries to find a css file for the report.
*
* Search is done in following order:
* <ol>
* <li> report specific stylesheet
* <li> configured stylesheet
* <li> installation default of stylesheet
* </ol>
*
* @retval css-filename if a css-file was found
* @retval empty-string if no css-file was found
*/
QString cssFileNameGet();
/**
* Subdirectory for html-resources of application.
*
* @see QStandardPaths
*/
QString m_resourceHtml;
/**
* Notation of @c reportstylesheet as used by:
* @code
* MyMoneyFile::instance()::value();
* @endcode
*/
QString m_reportStyleSheet;
/**
* Filename of default css file.
*/
QString m_cssFileDefault;
protected:
ReportTable(const MyMoneyReport &_report);
/**
* Constructs html header.
*
* @param title html title of report
* @param[in] includeCSS flag, whether the generated html has to include the css inline or whether
* the css is referenced as a link to a file
* @return html header
*/
QString renderHeader(const QString& title, const QByteArray &encoding, bool includeCSS);
/**
* Constructs html footer.
*
* @return html footer
*/
QString renderFooter();
/**
* Constructs the body of the report. Implemented by the concrete classes
* @see PivotTable
* @see ListTable
* @return QString with the html body of the report
*/
virtual QString renderHTML() const = 0;
MyMoneyReport m_config;
/**
* Does the report contain any non-base currency
*/
mutable bool m_containsNonBaseCurrency;
public:
virtual ~ReportTable() {}
/**
* Constructs a comma separated-file of the report. Implemented by the concrete classes
* @see PivotTable
* @see ListTable
*/
virtual QString renderCSV() const = 0;
/**
* Renders a graph from the report. Implemented by the concrete classes
* @see PivotTable
*/
virtual void drawChart(KReportChartView& view) const = 0;
virtual void dump(const QString& file, const QString& context = QString()) const = 0;
/**
* Creates the complete html document.
*
* @param widget parent widget
* @param encoding character set encoding
* @param title html title of report
* @param includeCSS flag, whether the generated html has
* to include the css inline or whether
* the css is referenced as a link to a file
*
* @return complete html document
*/
QString renderReport(const QString &type, const QByteArray& encoding, const QString& title, bool includeCSS = false);
};
}
#endif
// REPORTTABLE_H
diff --git a/kmymoney/reports/tests/pivotgrid-test.cpp b/kmymoney/reports/tests/pivotgrid-test.cpp
index 9a3ada974..1c3677649 100644
--- a/kmymoney/reports/tests/pivotgrid-test.cpp
+++ b/kmymoney/reports/tests/pivotgrid-test.cpp
@@ -1,171 +1,171 @@
/***************************************************************************
pivotgridtest.cpp
-------------------
copyright : (C) 2002-2005 by Thomas Baumgart
email : ipwizard@users.sourceforge.net
Ace Jones <ace.j@hotpop.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 "pivotgrid-test.h"
#include <QtTest/QtTest>
#include "reportstestcommon.h"
#include "pivotgrid.h"
using namespace reports;
using namespace test;
QTEST_GUILESS_MAIN(PivotGridTest)
void PivotGridTest::init()
{
storage = new MyMoneySeqAccessMgr;
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"), MyMoneyAccount::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset);
- acCredit = makeAccount(QString("Credit Card"), MyMoneyAccount::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability);
- acSolo = makeAccount(QString("Solo"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
- acParent = makeAccount(QString("Parent"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
- acChild = makeAccount(QString("Child"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
- acForeign = makeAccount(QString("Foreign"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
-
- acSecondChild = makeAccount(QString("Second Child"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
- acGrandChild1 = makeAccount(QString("Grand Child 1"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild);
- acGrandChild2 = makeAccount(QString("Grand Child 2"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild);
+ acChecking = makeAccount(QString("Checking Account"), eMyMoney::Account::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset);
+ acCredit = makeAccount(QString("Credit Card"), eMyMoney::Account::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability);
+ acSolo = makeAccount(QString("Solo"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+ acParent = makeAccount(QString("Parent"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+ acChild = makeAccount(QString("Child"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
+ acForeign = makeAccount(QString("Foreign"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+
+ acSecondChild = makeAccount(QString("Second Child"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
+ acGrandChild1 = makeAccount(QString("Grand Child 1"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild);
+ acGrandChild2 = makeAccount(QString("Grand Child 2"), eMyMoney::Account::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/pivottable-test.cpp b/kmymoney/reports/tests/pivottable-test.cpp
index 5f40b33c0..48e481a9a 100644
--- a/kmymoney/reports/tests/pivottable-test.cpp
+++ b/kmymoney/reports/tests/pivottable-test.cpp
@@ -1,1068 +1,1068 @@
/***************************************************************************
pivottabletest.cpp
-------------------
copyright : (C) 2002-2005 by Thomas Baumgart
email : ipwizard@users.sourceforge.net
Ace Jones <ace.j@hotpop.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 "pivottable-test.h"
#include <QList>
#include <QFile>
#include <QTest>
#include <QTextCodec>
// DOH, mmreport.h uses this without including it!!
#include "mymoneyaccount.h"
#include "mymoneysecurity.h"
#include "mymoneyprice.h"
#include "mymoneyreport.h"
#include "mymoneystatement.h"
#include "mymoneystoragedump.h"
#include "mymoneystoragexml.h"
#include "pivottable.h"
#include "reportstestcommon.h"
using namespace reports;
using namespace test;
QTEST_GUILESS_MAIN(PivotTableTest)
void PivotTableTest::init()
{
storage = new MyMoneySeqAccessMgr;
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"), MyMoneyAccount::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset);
- acCredit = makeAccount(QString("Credit Card"), MyMoneyAccount::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability);
- acSolo = makeAccount(QString("Solo"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
- acParent = makeAccount(QString("Parent"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
- acChild = makeAccount(QString("Child"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
- acForeign = makeAccount(QString("Foreign"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+ acChecking = makeAccount(QString("Checking Account"), eMyMoney::Account::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset);
+ acCredit = makeAccount(QString("Credit Card"), eMyMoney::Account::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability);
+ acSolo = makeAccount(QString("Solo"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+ acParent = makeAccount(QString("Parent"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+ acChild = makeAccount(QString("Child"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
+ acForeign = makeAccount(QString("Foreign"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
- acSecondChild = makeAccount(QString("Second Child"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
- acGrandChild1 = makeAccount(QString("Grand Child 1"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild);
- acGrandChild2 = makeAccount(QString("Grand Child 2"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild);
+ acSecondChild = makeAccount(QString("Second Child"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
+ acGrandChild1 = makeAccount(QString("Grand Child 1"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild);
+ acGrandChild2 = makeAccount(QString("Grand Child 2"), eMyMoney::Account::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"][acChecking][eActual][5] == moCheckingOpen);
QVERIFY(networth_f.m_grid["Asset"]["Checking Account"][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"][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::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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"), MyMoneyAccount::Checkings, openingBalance, QDate(2016, 1, 1), acAsset);
- auto ctBasicIncome = makeAccount(QString("Basic Income"), MyMoneyAccount::Income, MyMoneyMoney(), QDate(2016, 1, 1), acIncome);
- auto ctBasicExpense = makeAccount(QString("Basic Expense"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2016, 1, 1), acExpense);
+ auto acBasicAccount = makeAccount(QString("Basic Account"), eMyMoney::Account::Checkings, openingBalance, QDate(2016, 1, 1), acAsset);
+ auto ctBasicIncome = makeAccount(QString("Basic Income"), eMyMoney::Account::Income, MyMoneyMoney(), QDate(2016, 1, 1), acIncome);
+ auto ctBasicExpense = makeAccount(QString("Basic Expense"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2016, 1, 1), acExpense);
TransactionHelper t1(QDate(2016, 7, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-6200000), acBasicAccount, ctBasicIncome);
TransactionHelper t2(QDate(2016, 8, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-200000), acBasicAccount, ctBasicIncome);
TransactionHelper t3(QDate(2016, 9, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-200000), acBasicAccount, ctBasicIncome);
TransactionHelper t4(QDate(2016, 10, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome);
TransactionHelper t5(QDate(2016, 11, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome);
TransactionHelper t6(QDate(2016, 12, 1), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(100000), acBasicAccount, ctBasicExpense);
TransactionHelper t7(QDate(2017, 1, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome);
TransactionHelper t8(QDate(2017, 2, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome);
TransactionHelper t9(QDate(2017, 3, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome);
TransactionHelper t10(QDate(2017, 4, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome);
TransactionHelper t11(QDate(2017, 5, 1), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(4500000), acBasicAccount, ctBasicExpense);
TransactionHelper t12(QDate(2017, 6, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome);
TransactionHelper t13(QDate(2017, 7, 1), MyMoneySplit::ActionDeposit, 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"][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"][acBasicAccount][eActual][i] == openingBalance);
QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][acBasicAccount][eActual][7] == openingBalance + MyMoneyMoney(6200000));
QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][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"][acBasicAccount][eActual][0] == MyMoneyMoney(18700000)); // opening value is equall to the value after t6 transaction
QVERIFY(nt_opening2.m_grid["Asset"]["Basic Account"][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::ActionWithdrawal, 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"][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::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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"][acParent][eActual][2] == moParent);
QVERIFY(spending_f.m_grid["Expense"]["Parent"][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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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"), MyMoneyAccount::Checkings, moCanOpening, QDate(2003, 11, 15), acAsset, "CAD");
- QString acJpyChecking = makeAccount(QString("Japanese Checking"), MyMoneyAccount::Checkings, moJpyOpening, QDate(2003, 11, 15), acAsset, "JPY");
- QString acCanCash = makeAccount(QString("Canadian"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acForeign, "CAD");
- QString acJpyCash = makeAccount(QString("Japanese"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acForeign, "JPY");
+ QString acCanChecking = makeAccount(QString("Canadian Checking"), eMyMoney::Account::Checkings, moCanOpening, QDate(2003, 11, 15), acAsset, "CAD");
+ QString acJpyChecking = makeAccount(QString("Japanese Checking"), eMyMoney::Account::Checkings, moJpyOpening, QDate(2003, 11, 15), acAsset, "JPY");
+ QString acCanCash = makeAccount(QString("Canadian"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acForeign, "CAD");
+ QString acJpyCash = makeAccount(QString("Japanese"), eMyMoney::Account::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::ActionWithdrawal, MyMoneyMoney(moJpyTransaction), acJpyChecking, acJpyCash, "JPY");
TransactionHelper t2(QDate(2004, 3, 20), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moJpyTransaction), acJpyChecking, acJpyCash, "JPY");
TransactionHelper t3(QDate(2004, 4, 20), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moJpyTransaction), acJpyChecking, acJpyCash, "JPY");
TransactionHelper t4(QDate(2004, 2, 20), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moCanTransaction), acCanChecking, acCanCash, "CAD");
TransactionHelper t5(QDate(2004, 3, 20), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moCanTransaction), acCanChecking, acCanCash, "CAD");
TransactionHelper t6(QDate(2004, 4, 20), MyMoneySplit::ActionWithdrawal, 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<IMyMoneySerialize*>(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"][acCanCash][eActual][1] == (moCanTransaction*moCanPrice));
QVERIFY(spending_f.m_grid["Expense"]["Foreign"][acCanCash][eActual][2] == (moCanTransaction*moCanPrice));
QVERIFY(spending_f.m_grid["Expense"]["Foreign"][acCanCash][eActual][3] == (moCanTransaction*moCanPrice));
// test multiple foreign currencies under a common parent
QVERIFY(spending_f.m_grid["Expense"]["Foreign"][acJpyCash][eActual][1] == (moJpyTransaction*moJpyPrice));
QVERIFY(spending_f.m_grid["Expense"]["Foreign"][acJpyCash][eActual][2] == (moJpyTransaction*moJpyPrice));
QVERIFY(spending_f.m_grid["Expense"]["Foreign"][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"][acCanCash][eActual][1] == (moCanTransaction));
QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][acCanCash][eActual][2] == (moCanTransaction));
QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][acCanCash][eActual][3] == (moCanTransaction));
QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][acJpyCash][eActual][1] == (moJpyTransaction));
QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][acJpyCash][eActual][2] == (moJpyTransaction));
QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][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"][acCanChecking][eActual][1] == (moCanOpening*moCanPrice));
QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][acCanChecking][eActual][2] == ((moCanOpening - moCanTransaction)*moCanPrice));
QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][acCanChecking][eActual][3] == ((moCanOpening - moCanTransaction - moCanTransaction)*moCanPrice));
QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][acCanChecking][eActual][4] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice));
QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][acCanChecking][eActual][12] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice));
// test Stable currency price, fluctuating account balance
QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][1] == (moJpyOpening*moJpyPrice));
QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][2] == ((moJpyOpening - moJpyTransaction)*moJpyPrice));
QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][3] == ((moJpyOpening - moJpyTransaction - moJpyTransaction)*moJpyPrice));
QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][4] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice));
// test Fluctuating currency price, stable account balance
QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][5] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice2));
QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][6] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice3));
QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][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::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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"][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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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"][acParent][eActual][10] == moNoPayee);
QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moNoPayee);
}
// text
{
TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2(QDate(2004, 2, 1), MyMoneySplit::ActionDeposit, -moParent1, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 1), MyMoneySplit::ActionTransfer, moChild, acCredit, acChecking);
MyMoneyReport filter;
filter.setRowType(MyMoneyReport::eExpenseIncome);
filter.addType(MyMoneyTransactionFilter::payments);
XMLandback(filter);
PivotTable spending_f(filter);
QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo);
filter.clearTransactionFilter();
filter.addType(MyMoneyTransactionFilter::deposits);
XMLandback(filter);
PivotTable spending_f2(filter);
QVERIFY(spending_f2.m_grid.m_total[eActual].m_total == moParent1);
filter.clearTransactionFilter();
filter.addType(MyMoneyTransactionFilter::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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
QList<MyMoneySplit> splits = t1.splits();
splits[0].setReconcileFlag(MyMoneySplit::Cleared);
splits[1].setReconcileFlag(MyMoneySplit::Cleared);
t1.modifySplit(splits[0]);
t1.modifySplit(splits[1]);
t1.update();
splits.clear();
splits = t2.splits();
splits[0].setReconcileFlag(MyMoneySplit::Reconciled);
splits[1].setReconcileFlag(MyMoneySplit::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(MyMoneyTransactionFilter::cleared);
+ 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(MyMoneyTransactionFilter::reconciled);
+ 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(MyMoneyTransactionFilter::notReconciled);
+ 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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
QList<MyMoneySplit> 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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2y1(QDate(2003, 11, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3y1(QDate(2003, 12, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t1y2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2y2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3y2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t1y3(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2y3(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3y3(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, 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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, 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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2d1(QDate(2004, 7, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3d1(QDate(2004, 7, 4), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t1d2(QDate(2004, 7, 14), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2d2(QDate(2004, 7, 15), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3d2(QDate(2004, 7, 20), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t1d3(QDate(2004, 8, 2), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2d3(QDate(2004, 8, 3), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3d3(QDate(2004, 8, 4), MyMoneySplit::ActionWithdrawal, 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<MyMoneyReport::EColumnType>(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", MyMoneyAccount::Investment, moZero, QDate(2004, 1, 1), acAsset);
- acStock1 = makeAccount("Stock 1", MyMoneyAccount::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock1);
- acStock2 = makeAccount("Stock 2", MyMoneyAccount::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock2);
- acDividends = makeAccount("Dividends", MyMoneyAccount::Income, moZero, QDate(2004, 1, 1), acIncome);
+ acInvestment = makeAccount("Investment", eMyMoney::Account::Investment, moZero, QDate(2004, 1, 1), acAsset);
+ acStock1 = makeAccount("Stock 1", eMyMoney::Account::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock1);
+ acStock2 = makeAccount("Stock 2", eMyMoney::Account::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock2);
+ acDividends = makeAccount("Dividends", eMyMoney::Account::Income, moZero, QDate(2004, 1, 1), acIncome);
// Transactions
// Date Action Shares Price Stock Asset Income
InvTransactionHelper s1b1(QDate(2004, 2, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(100.00), acStock1, acChecking, QString());
InvTransactionHelper s1b2(QDate(2004, 3, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(110.00), acStock1, acChecking, QString());
InvTransactionHelper s1s1(QDate(2004, 4, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-200.00), MyMoneyMoney(120.00), acStock1, acChecking, QString());
InvTransactionHelper s1s2(QDate(2004, 5, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-200.00), MyMoneyMoney(100.00), acStock1, acChecking, QString());
InvTransactionHelper s1r1(QDate(2004, 6, 1), MyMoneySplit::ActionReinvestDividend, MyMoneyMoney(50.00), MyMoneyMoney(100.00), acStock1, QString(), acDividends);
InvTransactionHelper s1r2(QDate(2004, 7, 1), MyMoneySplit::ActionReinvestDividend, MyMoneyMoney(50.00), MyMoneyMoney(80.00), acStock1, QString(), acDividends);
InvTransactionHelper s1c1(QDate(2004, 8, 1), MyMoneySplit::ActionDividend, MyMoneyMoney(10.00), MyMoneyMoney(100.00), acStock1, acChecking, acDividends);
InvTransactionHelper s1c2(QDate(2004, 9, 1), MyMoneySplit::ActionDividend, 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<IMyMoneySerialize*>(MyMoneyFile::instance()->storage()));
g.close();
invtran.dump("invtran.html", "<html><head></head><body>%1</body></html>");
invhold.dump("invhold.html", "<html><head></head><body>%1</body></html>");
#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,
MyMoneyTransactionFilter::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,
MyMoneyTransactionFilter::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,
MyMoneyTransactionFilter::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,
MyMoneyTransactionFilter::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,
MyMoneyTransactionFilter::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("*<meta * charset=" + encoding + "*>*"));
rx.setPatternSyntax(QRegExp::Wildcard);
rx.setCaseSensitivity(Qt::CaseInsensitive);
QVERIFY(rx.exactMatch(html));
}
diff --git a/kmymoney/reports/tests/querytable-test.cpp b/kmymoney/reports/tests/querytable-test.cpp
index 19749330d..d29b43f5d 100644
--- a/kmymoney/reports/tests/querytable-test.cpp
+++ b/kmymoney/reports/tests/querytable-test.cpp
@@ -1,895 +1,895 @@
/***************************************************************************
querytabletest.cpp
-------------------
copyright : (C) 2002 by Thomas Baumgart
email : ipwizard@users.sourceforge.net
Ace Jones <ace.j@hotpop.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 "querytable-test.h"
#include <QFile>
#include <QTest>
#include <KLocalizedString>
#include "reportstestcommon.h"
#include "querytable.h"
#include "mymoneyaccount.h"
#include "mymoneysecurity.h"
#include "mymoneyprice.h"
#include "mymoneystoragedump.h"
#include "mymoneyreport.h"
#include "mymoneystatement.h"
#include "mymoneystoragexml.h"
using namespace reports;
using namespace test;
QTEST_GUILESS_MAIN(QueryTableTest)
void QueryTableTest::init()
{
storage = new MyMoneySeqAccessMgr;
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"), MyMoneyAccount::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset);
- acCredit = makeAccount(QString("Credit Card"), MyMoneyAccount::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability);
- acSolo = makeAccount(QString("Solo"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
- acParent = makeAccount(QString("Parent"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
- acChild = makeAccount(QString("Child"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
- acForeign = makeAccount(QString("Foreign"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
- acTax = makeAccount(QString("Tax"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2005, 1, 11), acExpense, "", true);
+ acChecking = makeAccount(QString("Checking Account"), eMyMoney::Account::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset);
+ acCredit = makeAccount(QString("Credit Card"), eMyMoney::Account::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability);
+ acSolo = makeAccount(QString("Solo"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+ acParent = makeAccount(QString("Parent"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+ acChild = makeAccount(QString("Child"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent);
+ acForeign = makeAccount(QString("Foreign"), eMyMoney::Account::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense);
+ acTax = makeAccount(QString("Tax"), eMyMoney::Account::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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
unsigned cols;
MyMoneyReport filter;
filter.setRowType(MyMoneyReport::eCategory);
cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount;
filter.setQueryColumns(static_cast<MyMoneyReport::EQueryColumns>(cols)); //
filter.setName("Transactions by Category");
XMLandback(filter);
QueryTable qtbl_1(filter);
writeTabletoHTML(qtbl_1, "Transactions by Category.html");
QList<ListTable::TableRow> 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<MyMoneyReport::EQueryColumns>(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<MyMoneyReport::EQueryColumns>(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<MyMoneyReport::EQueryColumns>(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<MyMoneyReport::EQueryColumns>(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<MyMoneyReport::EQueryColumns>(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("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"html/kmymoney.css\"></head><body>\n%1\n</body></html>\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<ListTable::TableRow> 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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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", MyMoneyAccount::Investment, moZero, QDate(2003, 11, 1), acAsset);
- acStock1 = makeAccount("Stock 1", MyMoneyAccount::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock1);
- acStock2 = makeAccount("Stock 2", MyMoneyAccount::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock2);
- acStock3 = makeAccount("Stock 3", MyMoneyAccount::Stock, moZero, QDate(2003, 11, 1), acInvestment, eqStock3);
- acStock4 = makeAccount("Stock 4", MyMoneyAccount::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock4);
- acDividends = makeAccount("Dividends", MyMoneyAccount::Income, moZero, QDate(2004, 1, 1), acIncome);
- acInterest = makeAccount("Interest", MyMoneyAccount::Income, moZero, QDate(2004, 1, 1), acIncome);
- acFees = makeAccount("Fees", MyMoneyAccount::Expense, moZero, QDate(2003, 11, 1), acExpense);
+ acInvestment = makeAccount("Investment", eMyMoney::Account::Investment, moZero, QDate(2003, 11, 1), acAsset);
+ acStock1 = makeAccount("Stock 1", eMyMoney::Account::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock1);
+ acStock2 = makeAccount("Stock 2", eMyMoney::Account::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock2);
+ acStock3 = makeAccount("Stock 3", eMyMoney::Account::Stock, moZero, QDate(2003, 11, 1), acInvestment, eqStock3);
+ acStock4 = makeAccount("Stock 4", eMyMoney::Account::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock4);
+ acDividends = makeAccount("Dividends", eMyMoney::Account::Income, moZero, QDate(2004, 1, 1), acIncome);
+ acInterest = makeAccount("Interest", eMyMoney::Account::Income, moZero, QDate(2004, 1, 1), acIncome);
+ acFees = makeAccount("Fees", eMyMoney::Account::Expense, moZero, QDate(2003, 11, 1), acExpense);
// Transactions
// Date Action Shares Price Stock Asset Income
InvTransactionHelper s1b1(QDate(2003, 12, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(100.00), acStock3, acChecking, QString());
InvTransactionHelper s1b2(QDate(2004, 1, 30), MyMoneySplit::ActionBuyShares, MyMoneyMoney(500.00), MyMoneyMoney(100.00), acStock4, acChecking, acFees, MyMoneyMoney(100.00));
InvTransactionHelper s1b3(QDate(2004, 1, 30), MyMoneySplit::ActionBuyShares, MyMoneyMoney(500.00), MyMoneyMoney(90.00), acStock4, acChecking, acFees, MyMoneyMoney(100.00));
InvTransactionHelper s1b4(QDate(2004, 2, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(100.00), acStock1, acChecking, QString());
InvTransactionHelper s1b5(QDate(2004, 3, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(110.00), acStock1, acChecking, QString());
InvTransactionHelper s1s1(QDate(2004, 4, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-200.00), MyMoneyMoney(120.00), acStock1, acChecking, QString());
InvTransactionHelper s1s2(QDate(2004, 5, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-200.00), MyMoneyMoney(100.00), acStock1, acChecking, QString());
InvTransactionHelper s1s3(QDate(2004, 5, 30), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-1000.00), MyMoneyMoney(120.00), acStock4, acChecking, acFees, MyMoneyMoney(200.00));
InvTransactionHelper s1r1(QDate(2004, 6, 1), MyMoneySplit::ActionReinvestDividend, MyMoneyMoney(50.00), MyMoneyMoney(100.00), acStock1, QString(), acDividends);
InvTransactionHelper s1r2(QDate(2004, 7, 1), MyMoneySplit::ActionReinvestDividend, MyMoneyMoney(50.00), MyMoneyMoney(80.00), acStock1, QString(), acDividends);
InvTransactionHelper s1c1(QDate(2004, 8, 1), MyMoneySplit::ActionDividend, MyMoneyMoney(10.00), MyMoneyMoney(100.00), acStock1, acChecking, acDividends);
InvTransactionHelper s1c2(QDate(2004, 9, 1), MyMoneySplit::ActionDividend, MyMoneyMoney(10.00), MyMoneyMoney(120.00), acStock1, acChecking, acDividends);
InvTransactionHelper s1y1(QDate(2004, 9, 15), MyMoneySplit::ActionYield, 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,
MyMoneyTransactionFilter::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<ListTable::TableRow> 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,
MyMoneyTransactionFilter::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<IMyMoneySerialize*>(MyMoneyFile::instance()->storage()));
g.close();
invtran.dump("invtran.html", "<html><head></head><body>%1</body></html>");
invhold.dump("invhold.html", "<html><head></head><body>%1</body></html>");
#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", MyMoneyAccount::Investment, moZero, QDate(2017, 8, 1), acAsset);
- acStock1 = makeAccount("Stock 1", MyMoneyAccount::Stock, moZero, QDate(2017, 8, 1), acInvestment, eqStock1);
+ acInvestment = makeAccount("Investment", eMyMoney::Account::Investment, moZero, QDate(2017, 8, 1), acAsset);
+ acStock1 = makeAccount("Stock 1", eMyMoney::Account::Stock, moZero, QDate(2017, 8, 1), acInvestment, eqStock1);
// Transactions
// Date Action Shares Price Stock Asset Income
InvTransactionHelper s1b1(QDate(2017, 8, 1), MyMoneySplit::ActionBuyShares, firstSharesPurchase, priceBeforeSplit, acStock1, acChecking, QString());
InvTransactionHelper s1s1(QDate(2017, 8, 2), MyMoneySplit::ActionSplitShares, splitFactor, MyMoneyMoney(), acStock1, QString(), QString());
InvTransactionHelper s1b2(QDate(2017, 8, 3), MyMoneySplit::ActionBuyShares, secondSharesPurchase, priceAfterSplit, acStock1, acChecking, QString());
//
// Investment Performance Report
//
MyMoneyReport invhold_r(
MyMoneyReport::eAccountByTopAccount,
MyMoneyReport::eQCperformance,
MyMoneyTransactionFilter::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"), MyMoneyAccount::Checkings, moZero, QDate(2017, 8, 1), acAsset, "CAD");
+ const auto acCadChecking = makeAccount(QString("Canadian Checking"), eMyMoney::Account::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::ActionWithdrawal, 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<MyMoneyReport::EQueryColumns>(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::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild);
TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent);
TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent);
TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, 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<MyMoneyReport::EQueryColumns>(cols)); //
XMLandback(filter);
QueryTable qtbl_3(filter);
writeTabletoHTML(qtbl_3, "Transactions by Account.html");
QString html = qtbl_3.renderHTML();
QList<ListTable::TableRow> 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 + "</td><td class=\"left0\"></td><td class=\"left0\">" + i18n("Opening Balance")) > 0);
QVERIFY(html.indexOf(closingDate + "</td><td class=\"left0\"></td><td class=\"left0\">" + i18n("Closing Balance") + "</td><td class=\"left0\"></td><td class=\"value\"></td><td>&nbsp;-702.36</td></tr>") > 0);
QVERIFY(html.indexOf(closingDate + "</td><td class=\"left0\"></td><td class=\"left0\">" + i18n("Closing Balance") + "</td><td class=\"left0\"></td><td class=\"value\"></td><td>&nbsp;-705.69</td></tr>") > 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"), MyMoneyAccount::Checkings, moJpyOpening, QDate(2003, 11, 15), acAsset, "JPY");
+ QString acJpyChecking = makeAccount(QString("Japanese Checking"), eMyMoney::Account::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::ActionTransfer, MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY");
TransactionHelper t4(openingDate, MyMoneySplit::ActionDeposit, MyMoneyMoney(moTransaction), acCredit, acChecking);
TransactionHelper t2(intermediateDate, MyMoneySplit::ActionTransfer, MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY");
TransactionHelper t5(intermediateDate, MyMoneySplit::ActionDeposit, MyMoneyMoney(moTransaction), acCredit, acChecking);
TransactionHelper t3(closingDate, MyMoneySplit::ActionTransfer, MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY");
TransactionHelper t6(closingDate, MyMoneySplit::ActionDeposit, MyMoneyMoney(moTransaction), acCredit, acChecking);
// test that an income/expense transaction that involves a currency exchange is properly reported
TransactionHelper t7(intermediateDate, MyMoneySplit::ActionWithdrawal, 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<MyMoneyReport::EQueryColumns>(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<ListTable::TableRow> 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 + "</td><td class=\"left0\"></td><td class=\"left0\">" + i18n("Opening Balance") + "</td><td class=\"left0\"></td><td class=\"value\"></td><td>USD&nbsp;0.00</td></tr>") > 0);
QVERIFY(html.indexOf(closingDateString + "</td><td class=\"left0\"></td><td class=\"left0\">" + i18n("Closing Balance") + "</td><td class=\"left0\"></td><td class=\"value\"></td><td>USD&nbsp;304.00</td></tr>") > 0);
QVERIFY(html.indexOf(closingDateString + "</td><td class=\"left0\"></td><td class=\"left0\">" + i18n("Closing Balance") + "</td><td class=\"left0\"></td><td class=\"value\"></td><td>USD&nbsp;-300.00</td></tr>") > 0);
QVERIFY(html.indexOf(closingDateString + "</td><td class=\"left0\"></td><td class=\"left0\">" + i18n("Closing Balance") + "</td><td class=\"left0\"></td><td class=\"value\"></td><td>JPY&nbsp;-400.00</td></tr>") > 0);
// after a transfer of 100 JPY the balance should be 1.00 - price is 0.010 (precision of 2)
QVERIFY(html.indexOf("<a href=ledger?id=A000001&tid=T000000000000000001>" + openingDateString + "</a></td><td class=\"left0\"></td><td class=\"left0\">Test Payee</td><td class=\"left0\">Transfer from Japanese Checking</td><td class=\"value\">USD&nbsp;1.00</td><td>USD&nbsp;1.00</td></tr>") > 0);
// after a transfer of 100 the balance should be 101.00
QVERIFY(html.indexOf("<a href=ledger?id=A000001&tid=T000000000000000002>" + openingDateString + "</a></td><td class=\"left0\"></td><td class=\"left0\">Test Payee</td><td class=\"left0\">Transfer from Credit Card</td><td class=\"value\">USD&nbsp;100.00</td><td>USD&nbsp;101.00</td></tr>") > 0);
// after a transfer of 100 JPY the balance should be 102.00 - price is 0.011 (precision of 2)
QVERIFY(html.indexOf("<a href=ledger?id=A000001&tid=T000000000000000003>" + intermediateDateString + "</a></td><td class=\"left0\"></td><td class=\"left0\">Test Payee</td><td class=\"left0\">Transfer from Japanese Checking</td><td class=\"value\">USD&nbsp;1.00</td><td>USD&nbsp;102.00</td></tr>") > 0);
// after a transfer of 100 the balance should be 202.00
QVERIFY(html.indexOf("<a href=ledger?id=A000001&tid=T000000000000000004>" + intermediateDateString + "</a></td><td class=\"left0\"></td><td class=\"left0\">Test Payee</td><td class=\"left0\">Transfer from Credit Card</td><td class=\"value\">USD&nbsp;100.00</td><td>USD&nbsp;202.00</td></tr>") > 0);
// after a transfer of 100 JPY the balance should be 204.00 - price is 0.024 (precision of 2)
QVERIFY(html.indexOf("<a href=ledger?id=A000001&tid=T000000000000000005>" + closingDateString + "</a></td><td class=\"left0\"></td><td class=\"left0\">Test Payee</td><td class=\"left0\">Transfer from Japanese Checking</td><td class=\"value\">USD&nbsp;2.00</td><td>USD&nbsp;204.00</td></tr>") > 0);
// after a transfer of 100 the balance should be 304.00
QVERIFY(html.indexOf("<a href=ledger?id=A000001&tid=T000000000000000006>" + closingDateString + "</a></td><td class=\"left0\"></td><td class=\"left0\">Test Payee</td><td class=\"left0\">Transfer from Credit Card</td><td class=\"value\">USD&nbsp;100.00</td><td>USD&nbsp;304.00</td></tr>") > 0);
// a 100.00 JPY withdrawal should be displayed as such even if the expense account uses another currency
QVERIFY(html.indexOf("<a href=ledger?id=A000008&tid=T000000000000000007>" + intermediateDateString + "</a></td><td class=\"left0\"></td><td class=\"left0\">Test Payee</td><td class=\"left0\">Solo</td><td class=\"value\">JPY&nbsp;-100.00</td><td>JPY&nbsp;-300.00</td></tr>") > 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 + "</td><td class=\"left0\"></td><td class=\"left0\">" + i18n("Opening Balance") + "</td><td class=\"left0\"></td><td class=\"value\"></td><td>&nbsp;0.00</td></tr>") > 0);
QVERIFY(html.indexOf(closingDateString + "</td><td class=\"left0\"></td><td class=\"left0\">" + i18n("Closing Balance") + "</td><td class=\"left0\"></td><td class=\"value\"></td><td>&nbsp;304.00</td></tr>") > 0);
QVERIFY(html.indexOf(closingDateString + "</td><td class=\"left0\"></td><td class=\"left0\">" + i18n("Closing Balance") + "</td><td class=\"left0\"></td><td class=\"value\"></td><td>&nbsp;-300.00</td></tr>") > 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 + "</td><td class=\"left0\"></td><td class=\"left0\">" + i18n("Closing Balance") + "</td><td class=\"left0\"></td><td class=\"value\"></td><td>&nbsp;-8.00</td></tr>") > 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("<a href=ledger?id=A000008&tid=T000000000000000001>" + openingDateString + "</a></td><td class=\"left0\"></td><td class=\"left0\">Test Payee</td><td class=\"left0\">Transfer to Checking Account</td><td class=\"value\">&nbsp;-1.00</td><td>&nbsp;-1.00</td></tr>") > 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("<a href=ledger?id=A000008&tid=T000000000000000003>" + intermediateDateString + "</a></td><td class=\"left0\"></td><td class=\"left0\">Test Payee</td><td class=\"left0\">Transfer to Checking Account</td><td class=\"value\">&nbsp;-1.00</td><td>&nbsp;-2.00</td></tr>") > 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("<a href=ledger?id=A000008&tid=T000000000000000005>" + closingDateString + "</a></td><td class=\"left0\"></td><td class=\"left0\">Test Payee</td><td class=\"left0\">Transfer to Checking Account</td><td class=\"value\">&nbsp;-2.00</td><td>&nbsp;-5.00</td></tr>") > 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("<a href=ledger?id=A000008&tid=T000000000000000007>" + intermediateDateString + "</a></td><td class=\"left0\"></td><td class=\"left0\">Test Payee</td><td class=\"left0\">Solo</td><td class=\"value\">&nbsp;-1.00</td><td>&nbsp;-3.00</td></tr>") > 0);
} catch (const MyMoneyException &e) {
QFAIL(qPrintable(e.what()));
}
}
void QueryTableTest::testTaxReport()
{
try {
TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo);
TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, 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<MyMoneyReport::EQueryColumns>(cols));
filter.setTax(true);
XMLandback(filter);
QueryTable qtbl_3(filter);
writeTabletoHTML(qtbl_3, "Tax Transactions.html");
QList<ListTable::TableRow> 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/reportstestcommon.cpp b/kmymoney/reports/tests/reportstestcommon.cpp
index db457318e..0cbd0d155 100644
--- a/kmymoney/reports/tests/reportstestcommon.cpp
+++ b/kmymoney/reports/tests/reportstestcommon.cpp
@@ -1,483 +1,483 @@
/***************************************************************************
reportstestcommon.cpp
-------------------
copyright : (C) 2002-2005 by Thomas Baumgart
email : ipwizard@users.sourceforge.net
Ace Jones <ace.j@hotpop.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 "kreportsview-test.h"
#include <QList>
#include <QFile>
#include <QTextStream>
#include "reportstestcommon.h"
#include "pivottable.h"
#include "querytable.h"
using namespace reports;
#include "mymoneysecurity.h"
#include "mymoneyprice.h"
#include "mymoneystoragedump.h"
#include "mymoneyreport.h"
#include "mymoneystatement.h"
#include "mymoneystoragexml.h"
namespace test
{
const MyMoneyMoney moCheckingOpen(0.0);
const MyMoneyMoney moCreditOpen(-0.0);
const MyMoneyMoney moConverterCheckingOpen(1418.0);
const MyMoneyMoney moConverterCreditOpen(-418.0);
const MyMoneyMoney moZero(0.0);
const MyMoneyMoney moSolo(234.12);
const MyMoneyMoney moParent1(88.01);
const MyMoneyMoney moParent2(133.22);
const MyMoneyMoney moParent(moParent1 + moParent2);
const MyMoneyMoney moChild(14.00);
const MyMoneyMoney moThomas(5.11);
const MyMoneyMoney moNoPayee(8944.70);
QString acAsset;
QString acLiability;
QString acExpense;
QString acIncome;
QString acChecking;
QString acCredit;
QString acSolo;
QString acParent;
QString acChild;
QString acSecondChild;
QString acGrandChild1;
QString acGrandChild2;
QString acForeign;
QString acCanChecking;
QString acJpyChecking;
QString acCanCash;
QString acJpyCash;
QString inBank;
QString eqStock1;
QString eqStock2;
QString eqStock3;
QString eqStock4;
QString acInvestment;
QString acStock1;
QString acStock2;
QString acStock3;
QString acStock4;
QString acDividends;
QString acInterest;
QString acFees;
QString acTax;
QString acCash;
TransactionHelper::TransactionHelper(const QDate& _date, const QString& _action, MyMoneyMoney _value, const QString& _accountid, const QString& _categoryid, const QString& _currencyid, const QString& _payee)
{
// _currencyid is the currency of the transaction, and of the _value
// both the account and category can have their own currency (athough the category having
// a foreign currency is not yet supported by the program, the reports will still allow it,
// so it must be tested.)
MyMoneyFile* file = MyMoneyFile::instance();
bool haspayee = ! _payee.isEmpty();
MyMoneyPayee payeeTest = file->payeeByName(_payee);
MyMoneyFileTransaction ft;
setPostDate(_date);
QString currencyid = _currencyid;
if (currencyid.isEmpty())
currencyid = MyMoneyFile::instance()->baseCurrency().id();
setCommodity(currencyid);
MyMoneyMoney price;
MyMoneySplit splitLeft;
if (haspayee)
splitLeft.setPayeeId(payeeTest.id());
splitLeft.setAction(_action);
splitLeft.setValue(-_value);
price = MyMoneyFile::instance()->price(currencyid, file->account(_accountid).currencyId(), _date).rate(file->account(_accountid).currencyId());
splitLeft.setShares(-_value * price);
splitLeft.setAccountId(_accountid);
addSplit(splitLeft);
MyMoneySplit splitRight;
if (haspayee)
splitRight.setPayeeId(payeeTest.id());
splitRight.setAction(_action);
splitRight.setValue(_value);
price = MyMoneyFile::instance()->price(currencyid, file->account(_categoryid).currencyId(), _date).rate(file->account(_categoryid).currencyId());
splitRight.setShares(_value * price);
splitRight.setAccountId(_categoryid);
addSplit(splitRight);
MyMoneyFile::instance()->addTransaction(*this);
ft.commit();
}
TransactionHelper::~TransactionHelper()
{
MyMoneyFileTransaction ft;
MyMoneyFile::instance()->removeTransaction(*this);
ft.commit();
}
void TransactionHelper::update()
{
MyMoneyFileTransaction ft;
MyMoneyFile::instance()->modifyTransaction(*this);
ft.commit();
}
InvTransactionHelper::InvTransactionHelper(const QDate& _date, const QString& _action, MyMoneyMoney _shares, MyMoneyMoney _price, const QString& _stockaccountid, const QString& _transferid, const QString& _categoryid, MyMoneyMoney _fee)
{
init(_date, _action, _shares, _price, _fee, _stockaccountid, _transferid, _categoryid);
}
void InvTransactionHelper::init(const QDate& _date, const QString& _action, MyMoneyMoney _shares, MyMoneyMoney _price, MyMoneyMoney _fee, const QString& _stockaccountid, const QString& _transferid, const QString& _categoryid)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount stockaccount = file->account(_stockaccountid);
MyMoneyMoney value = _shares * _price;
setPostDate(_date);
setCommodity("USD");
MyMoneySplit s1;
s1.setValue(value);
s1.setAccountId(_stockaccountid);
if (_action == MyMoneySplit::ActionReinvestDividend) {
s1.setShares(_shares);
s1.setAction(MyMoneySplit::ActionReinvestDividend);
MyMoneySplit s2;
s2.setAccountId(_categoryid);
s2.setShares(-value);
s2.setValue(-value);
addSplit(s2);
} else if (_action == MyMoneySplit::ActionDividend || _action == MyMoneySplit::ActionYield) {
s1.setAccountId(_categoryid);
s1.setShares(-value);
s1.setValue(-value);
// Split 2 will be the zero-amount investment split that serves to
// mark this transaction as a cash dividend and note which stock account
// it belongs to.
MyMoneySplit s2;
s2.setValue(MyMoneyMoney());
s2.setShares(MyMoneyMoney());
s2.setAction(_action);
s2.setAccountId(_stockaccountid);
addSplit(s2);
MyMoneySplit s3;
s3.setAccountId(_transferid);
s3.setShares(value);
s3.setValue(value);
addSplit(s3);
} else if (_action == MyMoneySplit::ActionBuyShares) {
s1.setShares(_shares);
s1.setValue(value);
s1.setAction(MyMoneySplit::ActionBuyShares);
MyMoneySplit s3;
s3.setAccountId(_transferid);
s3.setShares(-value - _fee);
s3.setValue(-value - _fee);
addSplit(s3);
if (!_categoryid.isEmpty() && !_fee.isZero()) {
MyMoneySplit s2;
s2.setAccountId(_categoryid);
s2.setValue(_fee);
s2.setShares(_fee);
addSplit(s2);
}
} else if (_action == MyMoneySplit::ActionSplitShares) {
s1.setShares(_shares.abs());
s1.setValue(MyMoneyMoney());
s1.setPrice(MyMoneyMoney());
}
addSplit(s1);
//qDebug() << "created transaction, now adding...";
MyMoneyFileTransaction ft;
file->addTransaction(*this);
//qDebug() << "updating price...";
// update the price, while we're here
if (_action != MyMoneySplit::ActionSplitShares) {
QString stockid = stockaccount.currencyId();
QString basecurrencyid = file->baseCurrency().id();
MyMoneyPrice price = file->price(stockid, basecurrencyid, _date, true);
if (!price.isValid()) {
MyMoneyPrice newprice(stockid, basecurrencyid, _date, _price, "test");
file->addPrice(newprice);
}
}
ft.commit();
//qDebug() << "successfully added " << id();
}
-QString makeAccount(const QString& _name, MyMoneyAccount::accountTypeE _type, MyMoneyMoney _balance, const QDate& _open, const QString& _parent, QString _currency, bool _taxReport, bool _openingBalance)
+QString makeAccount(const QString& _name, eMyMoney::Account _type, MyMoneyMoney _balance, const QDate& _open, const QString& _parent, QString _currency, bool _taxReport, bool _openingBalance)
{
MyMoneyAccount info;
MyMoneyFileTransaction ft;
info.setName(_name);
info.setAccountType(_type);
info.setOpeningDate(_open);
if (!_currency.isEmpty())
info.setCurrencyId(_currency);
else
info.setCurrencyId(MyMoneyFile::instance()->baseCurrency().id());
if (_taxReport)
info.setValue("Tax", "Yes");
if (_openingBalance)
info.setValue("OpeningBalanceAccount", "Yes");
MyMoneyAccount parent = MyMoneyFile::instance()->account(_parent);
MyMoneyFile::instance()->addAccount(info, parent);
// create the opening balance transaction if any
if (!_balance.isZero()) {
MyMoneySecurity sec = MyMoneyFile::instance()->currency(info.currencyId());
MyMoneyFile::instance()->openingBalanceAccount(sec);
MyMoneyFile::instance()->createOpeningBalanceTransaction(info, _balance);
}
ft.commit();
return info.id();
}
void makePrice(const QString& _currencyid, const QDate& _date, const MyMoneyMoney& _price)
{
MyMoneyFileTransaction ft;
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneySecurity curr = file->currency(_currencyid);
MyMoneyPrice price(_currencyid, file->baseCurrency().id(), _date, _price, "test");
file->addPrice(price);
ft.commit();
}
QString makeEquity(const QString& _name, const QString& _symbol)
{
MyMoneySecurity equity;
MyMoneyFileTransaction ft;
equity.setName(_name);
equity.setTradingSymbol(_symbol);
equity.setSmallestAccountFraction(1000);
equity.setSecurityType(eMyMoney::Security::None/*MyMoneyEquity::ETYPE_STOCK*/);
MyMoneyFile::instance()->addSecurity(equity);
ft.commit();
return equity.id();
}
void makeEquityPrice(const QString& _id, const QDate& _date, const MyMoneyMoney& _price)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyFileTransaction ft;
QString basecurrencyid = file->baseCurrency().id();
MyMoneyPrice price = file->price(_id, basecurrencyid, _date, true);
if (!price.isValid()) {
MyMoneyPrice newprice(_id, basecurrencyid, _date, _price, "test");
file->addPrice(newprice);
}
ft.commit();
}
void writeRCFtoXMLDoc(const MyMoneyReport& filter, QDomDocument* doc)
{
QDomProcessingInstruction instruct = doc->createProcessingInstruction(QString("xml"), QString("version=\"1.0\" encoding=\"utf-8\""));
doc->appendChild(instruct);
QDomElement root = doc->createElement("KMYMONEY-FILE");
doc->appendChild(root);
QDomElement reports = doc->createElement("REPORTS");
root.appendChild(reports);
QDomElement report = doc->createElement("REPORT");
filter.write(report, doc);
reports.appendChild(report);
}
void writeTabletoHTML(const PivotTable& table, const QString& _filename)
{
static unsigned filenumber = 1;
QString filename = _filename;
if (filename.isEmpty()) {
filename = QString("report-%1%2.html").arg((filenumber < 10) ? "0" : "").arg(filenumber);
++filenumber;
}
QFile g(filename);
g.open(QIODevice::WriteOnly);
QTextStream(&g) << table.renderHTML();
g.close();
}
void writeTabletoHTML(const QueryTable& table, const QString& _filename)
{
static unsigned filenumber = 1;
QString filename = _filename;
if (filename.isEmpty()) {
filename = QString("report-%1%2.html").arg((filenumber < 10) ? "0" : "").arg(filenumber);
++filenumber;
}
QFile g(filename);
g.open(QIODevice::WriteOnly);
QTextStream(&g) << table.renderHTML();
g.close();
}
void writeTabletoCSV(const PivotTable& table, const QString& _filename)
{
static unsigned filenumber = 1;
QString filename = _filename;
if (filename.isEmpty()) {
filename = QString("report-%1%2.csv").arg((filenumber < 10) ? "0" : "").arg(filenumber);
++filenumber;
}
QFile g(filename);
g.open(QIODevice::WriteOnly);
QTextStream(&g) << table.renderCSV();
g.close();
}
void writeTabletoCSV(const QueryTable& table, const QString& _filename)
{
static unsigned filenumber = 1;
QString filename = _filename;
if (filename.isEmpty()) {
filename = QString("qreport-%1%2.csv").arg((filenumber < 10) ? "0" : "").arg(filenumber);
++filenumber;
}
QFile g(filename);
g.open(QIODevice::WriteOnly);
QTextStream(&g) << table.renderCSV();
g.close();
}
void writeRCFtoXML(const MyMoneyReport& filter, const QString& _filename)
{
static unsigned filenum = 1;
QString filename = _filename;
if (filename.isEmpty()) {
filename = QString("report-%1%2.xml").arg(QString::number(filenum).rightJustified(2, '0'));
++filenum;
}
QDomDocument* doc = new QDomDocument("KMYMONEY-FILE");
Q_CHECK_PTR(doc);
writeRCFtoXMLDoc(filter, doc);
QFile g(filename);
g.open(QIODevice::WriteOnly);
QTextStream stream(&g);
stream.setCodec("UTF-8");
stream << doc->toString();
g.close();
delete doc;
}
bool readRCFfromXMLDoc(QList<MyMoneyReport>& list, QDomDocument* doc)
{
bool result = false;
QDomElement rootElement = doc->documentElement();
if (!rootElement.isNull()) {
QDomNode child = rootElement.firstChild();
while (!child.isNull() && child.isElement()) {
QDomElement childElement = child.toElement();
if ("REPORTS" == childElement.tagName()) {
result = true;
QDomNode subchild = child.firstChild();
while (!subchild.isNull() && subchild.isElement()) {
MyMoneyReport filter;
if (filter.read(subchild.toElement())) {
list += filter;
}
subchild = subchild.nextSibling();
}
}
child = child.nextSibling();
}
}
return result;
}
bool readRCFfromXML(QList<MyMoneyReport>& list, const QString& filename)
{
int result = false;
QFile f(filename);
f.open(QIODevice::ReadOnly);
QDomDocument* doc = new QDomDocument;
if (doc->setContent(&f, false)) {
result = readRCFfromXMLDoc(list, doc);
}
delete doc;
return result;
}
void XMLandback(MyMoneyReport& filter)
{
// this function writes the filter to XML, and then reads
// it back from XML overwriting the original filter;
// in all cases, the result should be the same if the read
// & write methods are working correctly.
QDomDocument* doc = new QDomDocument("KMYMONEY-FILE");
Q_CHECK_PTR(doc);
writeRCFtoXMLDoc(filter, doc);
QList<MyMoneyReport> list;
if (readRCFfromXMLDoc(list, doc) && !list.isEmpty())
filter = list[0];
else
throw MYMONEYEXCEPTION("Failed to load report from XML");
delete doc;
}
MyMoneyMoney searchHTML(const QString& _html, const QString& _search)
{
QRegExp re(QString("%1[<>/td]*([\\-.0-9,]*)").arg(_search));
re.indexIn(_html);
QString found = re.cap(1);
found.remove(',');
return MyMoneyMoney(found.toDouble());
}
} // end namespace test
diff --git a/kmymoney/reports/tests/reportstestcommon.h b/kmymoney/reports/tests/reportstestcommon.h
index ccffbb13f..1ba4c03b9 100644
--- a/kmymoney/reports/tests/reportstestcommon.h
+++ b/kmymoney/reports/tests/reportstestcommon.h
@@ -1,137 +1,137 @@
/***************************************************************************
reportstestcommon.h
-------------------
copyright : (C) 2002-2005 by Thomas Baumgart
email : ipwizard@users.sourceforge.net
Ace Jones <ace.j@hotpop.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 REPORTSTESTCOMMON_H
#define REPORTSTESTCOMMON_H
#include <QList>
class QDomDocument;
#include "mymoneyaccount.h"
#include "mymoneytransaction.h"
#include "mymoneymoney.h"
class MyMoneyReport;
namespace reports
{
class PivotTable;
class QueryTable;
}
namespace test
{
extern const MyMoneyMoney moCheckingOpen;
extern const MyMoneyMoney moCreditOpen;
extern const MyMoneyMoney moConverterCheckingOpen;
extern const MyMoneyMoney moConverterCreditOpen;
extern const MyMoneyMoney moZero;
extern const MyMoneyMoney moSolo;
extern const MyMoneyMoney moParent1;
extern const MyMoneyMoney moParent2;
extern const MyMoneyMoney moParent;
extern const MyMoneyMoney moChild;
extern const MyMoneyMoney moThomas;
extern const MyMoneyMoney moNoPayee;
extern QString acAsset;
extern QString acLiability;
extern QString acExpense;
extern QString acIncome;
extern QString acChecking;
extern QString acCredit;
extern QString acSolo;
extern QString acParent;
extern QString acChild;
extern QString acSecondChild;
extern QString acGrandChild1;
extern QString acGrandChild2;
extern QString acForeign;
extern QString acCanChecking;
extern QString acJpyChecking;
extern QString acCanCash;
extern QString acJpyCash;
extern QString inBank;
extern QString eqStock1;
extern QString eqStock2;
extern QString eqStock3;
extern QString eqStock4;
extern QString acInvestment;
extern QString acStock1;
extern QString acStock2;
extern QString acStock3;
extern QString acStock4;
extern QString acDividends;
extern QString acInterest;
extern QString acFees;
extern QString acTax;
extern QString acCash;
class TransactionHelper: public MyMoneyTransaction
{
private:
QString m_id;
public:
TransactionHelper(const QDate& _date, const QString& _action, MyMoneyMoney _value, const QString& _accountid, const QString& _categoryid, const QString& _currencyid = QString(), const QString& _payee = "Test Payee");
~TransactionHelper();
void update();
protected:
TransactionHelper() {}
};
class InvTransactionHelper: public TransactionHelper
{
public:
InvTransactionHelper(const QDate& _date, const QString& _action, MyMoneyMoney _shares, MyMoneyMoney _value, const QString& _stockaccountid, const QString& _transferid, const QString& _categoryid, MyMoneyMoney _fee = MyMoneyMoney());
void init(const QDate& _date, const QString& _action, MyMoneyMoney _shares, MyMoneyMoney _price, MyMoneyMoney _fee, const QString& _stockaccountid, const QString& _transferid, const QString& _categoryid);
};
class BudgetEntryHelper
{
private:
QDate m_date;
QString m_categoryid;
MyMoneyMoney m_amount;
public:
BudgetEntryHelper() {}
BudgetEntryHelper(const QDate& _date, const QString& _categoryid, bool /* _applytosub */, const MyMoneyMoney& _amount): m_date(_date), m_categoryid(_categoryid), m_amount(_amount) {}
};
class BudgetHelper: public QList<BudgetEntryHelper>
{
MyMoneyMoney budgetAmount(const QDate& _date, const QString& _categoryid, bool& _applytosub);
};
-extern QString makeAccount(const QString& _name, MyMoneyAccount::accountTypeE _type, MyMoneyMoney _balance, const QDate& _open, const QString& _parent, QString _currency = "", bool _taxReport = false, bool _openingBalance = false);
+extern QString makeAccount(const QString& _name, eMyMoney::Account _type, MyMoneyMoney _balance, const QDate& _open, const QString& _parent, QString _currency = "", bool _taxReport = false, bool _openingBalance = false);
extern void makePrice(const QString& _currencyid, const QDate& _date, const MyMoneyMoney& _price);
QString makeEquity(const QString& _name, const QString& _symbol);
extern void makeEquityPrice(const QString& _id, const QDate& _date, const MyMoneyMoney& _price);
extern void writeRCFtoXMLDoc(const MyMoneyReport& filter, QDomDocument* doc);
extern void writeTabletoHTML(const reports::PivotTable& table, const QString& _filename = QString());
extern void writeTabletoHTML(const reports::QueryTable& table, const QString& _filename = QString());
extern void writeTabletoCSV(const reports::PivotTable& table, const QString& _filename = QString());
extern void writeTabletoCSV(const reports::QueryTable& table, const QString& _filename = QString());
extern void writeRCFtoXML(const MyMoneyReport& filter, const QString& _filename = QString());
extern bool readRCFfromXMLDoc(QList<MyMoneyReport>& list, QDomDocument* doc);
extern bool readRCFfromXML(QList<MyMoneyReport>& list, const QString& filename);
extern void XMLandback(MyMoneyReport& filter);
extern MyMoneyMoney searchHTML(const QString& _html, const QString& _search);
} // end namespace test
#endif // REPORTSTESTCOMMON_H
diff --git a/kmymoney/views/kaccountsview.cpp b/kmymoney/views/kaccountsview.cpp
index bd4d30c8f..79805dd69 100644
--- a/kmymoney/views/kaccountsview.cpp
+++ b/kmymoney/views/kaccountsview.cpp
@@ -1,109 +1,109 @@
/***************************************************************************
kaccountsview.cpp
-------------------
copyright : (C) 2007 by Thomas Baumgart <ipwizard@users.sourceforge.net>
(C) 2017 by Łukasz Wojniłowicz <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 "kaccountsview.h"
#include "kaccountsview_p.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QTimer>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneyglobalsettings.h"
using namespace Icons;
KAccountsView::KAccountsView(QWidget *parent) :
KMyMoneyAccountsViewBase(*new KAccountsViewPrivate(this), parent)
{
}
KAccountsView::KAccountsView(KAccountsViewPrivate &dd, QWidget *parent)
: KMyMoneyAccountsViewBase(dd, parent)
{
}
KAccountsView::~KAccountsView()
{
}
void KAccountsView::setDefaultFocus()
{
Q_D(KAccountsView);
QTimer::singleShot(0, d->ui->m_accountTree, SLOT(setFocus()));
}
void KAccountsView::refresh()
{
Q_D(KAccountsView);
if (!isVisible()) {
d->m_needsRefresh = true;
return;
}
d->m_needsRefresh = false;
// TODO: check why the invalidate is needed here
d->m_proxyModel->invalidate();
d->m_proxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts());
d->m_proxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode());
if (KMyMoneyGlobalSettings::showCategoriesInAccountsView()) {
- d->m_proxyModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Income, MyMoneyAccount::Expense});
+ d->m_proxyModel->addAccountGroup(QVector<eMyMoney::Account> {eMyMoney::Account::Income, eMyMoney::Account::Expense});
} else {
- d->m_proxyModel->removeAccountType(MyMoneyAccount::Income);
- d->m_proxyModel->removeAccountType(MyMoneyAccount::Expense);
+ d->m_proxyModel->removeAccountType(eMyMoney::Account::Income);
+ d->m_proxyModel->removeAccountType(eMyMoney::Account::Expense);
}
// reinitialize the default state of the hidden categories label
d->m_haveUnusedCategories = false;
d->ui->m_hiddenCategories->hide(); // hides label
d->m_proxyModel->setHideUnusedIncomeExpenseAccounts(KMyMoneyGlobalSettings::hideUnusedCategory());
}
void KAccountsView::showEvent(QShowEvent * event)
{
Q_D(KAccountsView);
if (!d->m_proxyModel)
d->init();
emit aboutToShow(View::Accounts);
if (d->m_needsRefresh)
refresh();
// don't forget base class implementation
QWidget::showEvent(event);
}
/**
* The view is notified that an unused income expense account has been hidden.
*/
void KAccountsView::slotUnusedIncomeExpenseAccountHidden()
{
Q_D(KAccountsView);
d->m_haveUnusedCategories = true;
d->ui->m_hiddenCategories->setVisible(d->m_haveUnusedCategories);
}
void KAccountsView::slotNetWorthChanged(const MyMoneyMoney &netWorth)
{
Q_D(KAccountsView);
d->netBalProChanged(netWorth, d->ui->m_totalProfitsLabel, View::Accounts);
}
diff --git a/kmymoney/views/kforecastview.cpp b/kmymoney/views/kforecastview.cpp
index 423959d01..4778dfa2b 100644
--- a/kmymoney/views/kforecastview.cpp
+++ b/kmymoney/views/kforecastview.cpp
@@ -1,1061 +1,1063 @@
/***************************************************************************
kforecastview.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 "kforecastview.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QTabWidget>
#include <QLabel>
#include <QLayout>
#include <QList>
#include <QIcon>
#include <QTimer>
#include <QScrollBar>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KTextEdit>
#include <KSharedConfig>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyutils.h"
#include "mymoneyfile.h"
+#include "mymoneyprice.h"
+#include "mymoneysecurity.h"
#include "kmymoneyglobalsettings.h"
#include "mymoneyforecast.h"
#include "mymoneybudget.h"
#include "pivottable.h"
#include "fixedcolumntreeview.h"
#include "kreportchartview.h"
#include "reportaccount.h"
#include "icons.h"
using namespace reports;
using namespace Icons;
KForecastView::KForecastView(QWidget *parent) :
QWidget(parent),
m_needLoad(true),
m_totalItem(0),
m_assetItem(0),
m_liabilityItem(0),
m_incomeItem(0),
m_expenseItem(0),
m_chartLayout(0),
m_forecastChart(0)
{
}
KForecastView::~KForecastView()
{
}
void KForecastView::setDefaultFocus()
{
QTimer::singleShot(0, m_forecastButton, SLOT(setFocus()));
}
void KForecastView::init()
{
m_needLoad = false;
setupUi(this);
m_forecastChart = new KReportChartView(m_tabChart);
for (int i = 0; i < MaxViewTabs; ++i)
m_needReload[i] = false;
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup grp = config->group("Last Use Settings");
m_tab->setCurrentIndex(grp.readEntry("KForecastView_LastType", 0));
m_forecastButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewForecast]));
connect(m_tab, SIGNAL(currentChanged(int)), this, SLOT(slotTabChanged(int)));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadForecast()));
connect(m_forecastButton, SIGNAL(clicked()), this, SLOT(slotManualForecast()));
m_forecastList->setUniformRowHeights(true);
m_forecastList->setAllColumnsShowFocus(true);
m_summaryList->setAllColumnsShowFocus(true);
m_budgetList->setAllColumnsShowFocus(true);
m_advancedList->setAlternatingRowColors(true);
connect(m_forecastList, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(itemExpanded(QTreeWidgetItem*)));
connect(m_forecastList, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(itemCollapsed(QTreeWidgetItem*)));
connect(m_summaryList, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(itemExpanded(QTreeWidgetItem*)));
connect(m_summaryList, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(itemCollapsed(QTreeWidgetItem*)));
connect(m_budgetList, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(itemExpanded(QTreeWidgetItem*)));
connect(m_budgetList, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(itemCollapsed(QTreeWidgetItem*)));
m_forecastChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_chartLayout = m_tabChart->layout();
m_chartLayout->setSpacing(6);
m_chartLayout->addWidget(m_forecastChart);
loadForecastSettings();
}
void KForecastView::slotTabChanged(int index)
{
ForecastViewTab tab = static_cast<ForecastViewTab>(index);
// remember this setting for startup
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup grp = config->group("Last Use Settings");
grp.writeEntry("KForecastView_LastType", QVariant(tab).toString());
loadForecast(tab);
}
void KForecastView::loadForecast(ForecastViewTab tab)
{
if (m_needReload[tab]) {
switch (tab) {
case ListView:
loadListView();
break;
case SummaryView:
loadSummaryView();
break;
case AdvancedView:
loadAdvancedView();
break;
case BudgetView:
loadBudgetView();
break;
case ChartView:
loadChartView();
break;
default:
break;
}
m_needReload[tab] = false;
}
}
void KForecastView::showEvent(QShowEvent* event)
{
if (m_needLoad) {
init();
loadForecastSettings();
}
emit aboutToShow();
slotTabChanged(m_tab->currentIndex());
// don't forget base class implementation
QWidget::showEvent(event);
}
void KForecastView::slotLoadForecast()
{
m_needReload[SummaryView] = true;
m_needReload[ListView] = true;
m_needReload[AdvancedView] = true;
m_needReload[BudgetView] = true;
m_needReload[ChartView] = true;
if (isVisible()) {
//refresh settings
loadForecastSettings();
slotTabChanged(m_tab->currentIndex());
}
}
void KForecastView::slotManualForecast()
{
m_needReload[SummaryView] = true;
m_needReload[ListView] = true;
m_needReload[AdvancedView] = true;
m_needReload[BudgetView] = true;
m_needReload[ChartView] = true;
if (isVisible())
slotTabChanged(m_tab->currentIndex());
}
void KForecastView::loadForecastSettings()
{
//fill the settings controls
m_forecastDays->setValue(KMyMoneyGlobalSettings::forecastDays());
m_accountsCycle->setValue(KMyMoneyGlobalSettings::forecastAccountCycle());
m_beginDay->setValue(KMyMoneyGlobalSettings::beginForecastDay());
m_forecastCycles->setValue(KMyMoneyGlobalSettings::forecastCycles());
m_historyMethod->setId(radioButton11, 0); // simple moving avg
m_historyMethod->setId(radioButton12, 1); // weighted moving avg
m_historyMethod->setId(radioButton13, 2); // linear regression
m_historyMethod->button(KMyMoneyGlobalSettings::historyMethod())->setChecked(true);
switch (KMyMoneyGlobalSettings::forecastMethod()) {
case 0:
m_forecastMethod->setText(i18nc("Scheduled method", "Scheduled"));
m_forecastCycles->setDisabled(true);
m_historyMethodGroupBox->setDisabled(true);
break;
case 1:
m_forecastMethod->setText(i18nc("History-based method", "History"));
m_forecastCycles->setEnabled(true);
m_historyMethodGroupBox->setEnabled(true);
break;
default:
m_forecastMethod->setText(i18nc("Unknown forecast method", "Unknown"));
break;
}
}
void KForecastView::loadListView()
{
MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast();
MyMoneyFile* file = MyMoneyFile::instance();
//get the settings from current page
forecast.setForecastDays(m_forecastDays->value());
forecast.setAccountsCycle(m_accountsCycle->value());
forecast.setBeginForecastDay(m_beginDay->value());
forecast.setForecastCycles(m_forecastCycles->value());
forecast.setHistoryMethod(m_historyMethod->checkedId());
forecast.doForecast();
m_forecastList->clear();
m_forecastList->setColumnCount(0);
m_forecastList->setIconSize(QSize(22, 22));
m_forecastList->setSortingEnabled(true);
m_forecastList->sortByColumn(0, Qt::AscendingOrder);
//add columns
QStringList headerLabels;
headerLabels << i18n("Account");
//add cycle interval columns
headerLabels << i18nc("Today's forecast", "Current");
for (int i = 1; i <= forecast.forecastDays(); ++i) {
QDate forecastDate = QDate::currentDate().addDays(i);
headerLabels << QLocale().toString(forecastDate, QLocale::LongFormat);
}
//add variation columns
headerLabels << i18n("Total variation");
//set the columns
m_forecastList->setHeaderLabels(headerLabels);
//add default rows
addTotalRow(m_forecastList, forecast);
addAssetLiabilityRows(forecast);
//load asset and liability forecast accounts
loadAccounts(forecast, file->asset(), m_assetItem, eDetailed);
loadAccounts(forecast, file->liability(), m_liabilityItem, eDetailed);
adjustHeadersAndResizeToContents(m_forecastList);
// add the fixed column only if the horizontal scroll bar is visible
m_fixedColumnView.reset(m_forecastList->horizontalScrollBar()->isVisible() ? new FixedColumnTreeView(m_forecastList) : 0);
}
void KForecastView::loadSummaryView()
{
MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast();
QList<MyMoneyAccount> accList;
int dropMinimum;
int dropZero;
MyMoneyFile* file = MyMoneyFile::instance();
//get the settings from current page
forecast.setForecastDays(m_forecastDays->value());
forecast.setAccountsCycle(m_accountsCycle->value());
forecast.setBeginForecastDay(m_beginDay->value());
forecast.setForecastCycles(m_forecastCycles->value());
forecast.setHistoryMethod(m_historyMethod->checkedId());
forecast.doForecast();
//add columns
QStringList headerLabels;
headerLabels << i18n("Account");
headerLabels << i18nc("Today's forecast", "Current");
//if beginning of forecast is today, set the begin day to next cycle to avoid repeating the first cycle
int daysToBeginDay;
if (QDate::currentDate() < forecast.beginForecastDate()) {
daysToBeginDay = QDate::currentDate().daysTo(forecast.beginForecastDate());
} else {
daysToBeginDay = forecast.accountsCycle();
}
for (int i = 0; ((i*forecast.accountsCycle()) + daysToBeginDay) <= forecast.forecastDays(); ++i) {
int intervalDays = ((i * forecast.accountsCycle()) + daysToBeginDay);
headerLabels << i18np("1 day", "%1 days", intervalDays);
}
//add variation columns
headerLabels << i18n("Total variation");
m_summaryList->clear();
//set the columns
m_summaryList->setHeaderLabels(headerLabels);
m_summaryList->setIconSize(QSize(22, 22));
m_summaryList->setSortingEnabled(true);
m_summaryList->sortByColumn(0, Qt::AscendingOrder);
//add default rows
addTotalRow(m_summaryList, forecast);
addAssetLiabilityRows(forecast);
loadAccounts(forecast, file->asset(), m_assetItem, eSummary);
loadAccounts(forecast, file->liability(), m_liabilityItem, eSummary);
adjustHeadersAndResizeToContents(m_summaryList);
//Add comments to the advice list
m_adviceText->clear();
//Get all accounts of the right type to calculate forecast
m_nameIdx.clear();
accList = forecast.accountList();
QList<MyMoneyAccount>::const_iterator accList_t = accList.constBegin();
for (; accList_t != accList.constEnd(); ++accList_t) {
MyMoneyAccount acc = *accList_t;
if (m_nameIdx[acc.id()] != acc.id()) { //Check if the account is there
m_nameIdx[acc.id()] = acc.id();
}
}
QMap<QString, QString>::ConstIterator it_nc;
for (it_nc = m_nameIdx.constBegin(); it_nc != m_nameIdx.constEnd(); ++it_nc) {
const MyMoneyAccount& acc = file->account(*it_nc);
MyMoneySecurity currency;
//change currency to deep currency if account is an investment
if (acc.isInvest()) {
MyMoneySecurity underSecurity = file->security(acc.currencyId());
currency = file->security(underSecurity.tradingCurrency());
} else {
currency = file->security(acc.currencyId());
}
//Check if the account is going to be below zero or below the minimal balance in the forecast period
QString minimumBalance = acc.value("minimumBalance");
MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance);
//Check if the account is going to be below minimal balance
dropMinimum = forecast.daysToMinimumBalance(acc);
//Check if the account is going to be below zero in the future
dropZero = forecast.daysToZeroBalance(acc);
// spit out possible warnings
QString msg;
// if a minimum balance has been specified, an appropriate warning will
// only be shown, if the drop below 0 is on a different day or not present
if (dropMinimum != -1
&& !minBalance.isZero()
&& (dropMinimum < dropZero
|| dropZero == -1)) {
switch (dropMinimum) {
case -1:
break;
case 0:
msg = QString("<font color=\"%1\">").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name());
msg += i18n("The balance of %1 is below the minimum balance %2 today.", acc.name(), MyMoneyUtils::formatMoney(minBalance, acc, currency));
msg += QString("</font>");
break;
default:
msg = QString("<font color=\"%1\">").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name());
msg += i18np("The balance of %2 will drop below the minimum balance %3 in %1 day.",
"The balance of %2 will drop below the minimum balance %3 in %1 days.",
dropMinimum - 1, acc.name(), MyMoneyUtils::formatMoney(minBalance, acc, currency));
msg += QString("</font>");
}
if (!msg.isEmpty()) {
m_adviceText->append(msg);
}
}
// a drop below zero is always shown
msg.clear();
switch (dropZero) {
case -1:
break;
case 0:
- if (acc.accountGroup() == MyMoneyAccount::Asset) {
+ if (acc.accountGroup() == eMyMoney::Account::Asset) {
msg = QString("<font color=\"%1\">").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name());
msg += i18n("The balance of %1 is below %2 today.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency));
msg += QString("</font>");
break;
}
- if (acc.accountGroup() == MyMoneyAccount::Liability) {
+ if (acc.accountGroup() == eMyMoney::Account::Liability) {
msg = i18n("The balance of %1 is above %2 today.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency));
break;
}
break;
default:
- if (acc.accountGroup() == MyMoneyAccount::Asset) {
+ if (acc.accountGroup() == eMyMoney::Account::Asset) {
msg = QString("<font color=\"%1\">").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name());
msg += i18np("The balance of %2 will drop below %3 in %1 day.",
"The balance of %2 will drop below %3 in %1 days.",
dropZero, acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency));
msg += QString("</font>");
break;
}
- if (acc.accountGroup() == MyMoneyAccount::Liability) {
+ if (acc.accountGroup() == eMyMoney::Account::Liability) {
msg = i18np("The balance of %2 will raise above %3 in %1 day.",
"The balance of %2 will raise above %3 in %1 days.",
dropZero, acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency));
break;
}
}
if (!msg.isEmpty()) {
m_adviceText->append(msg);
}
//advice about trends
msg.clear();
MyMoneyMoney accCycleVariation = forecast.accountCycleVariation(acc);
if (accCycleVariation < MyMoneyMoney()) {
msg = QString("<font color=\"%1\">").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name());
msg += i18n("The account %1 is decreasing %2 per cycle.", acc.name(), MyMoneyUtils::formatMoney(accCycleVariation, acc, currency));
msg += QString("</font>");
}
if (!msg.isEmpty()) {
m_adviceText->append(msg);
}
}
m_adviceText->show();
}
void KForecastView::loadAdvancedView()
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyAccount> accList;
MyMoneySecurity baseCurrency = file->baseCurrency();
MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast();
int daysToBeginDay;
//get the settings from current page
forecast.setForecastDays(m_forecastDays->value());
forecast.setAccountsCycle(m_accountsCycle->value());
forecast.setBeginForecastDay(m_beginDay->value());
forecast.setForecastCycles(m_forecastCycles->value());
forecast.setHistoryMethod(m_historyMethod->checkedId());
forecast.doForecast();
//Get all accounts of the right type to calculate forecast
m_nameIdx.clear();
accList = forecast.accountList();
QList<MyMoneyAccount>::const_iterator accList_t = accList.constBegin();
for (; accList_t != accList.constEnd(); ++accList_t) {
MyMoneyAccount acc = *accList_t;
if (m_nameIdx[acc.id()] != acc.id()) { //Check if the account is there
m_nameIdx[acc.id()] = acc.id();
}
}
//clear the list, including columns
m_advancedList->clear();
m_advancedList->setColumnCount(0);
m_advancedList->setIconSize(QSize(22, 22));
QStringList headerLabels;
//add first column of both lists
headerLabels << i18n("Account");
//if beginning of forecast is today, set the begin day to next cycle to avoid repeating the first cycle
if (QDate::currentDate() < forecast.beginForecastDate()) {
daysToBeginDay = QDate::currentDate().daysTo(forecast.beginForecastDate());
} else {
daysToBeginDay = forecast.accountsCycle();
}
//add columns
for (int i = 1; ((i * forecast.accountsCycle()) + daysToBeginDay) <= forecast.forecastDays(); ++i) {
headerLabels << i18n("Min Bal %1", i);
headerLabels << i18n("Min Date %1", i);
}
for (int i = 1; ((i * forecast.accountsCycle()) + daysToBeginDay) <= forecast.forecastDays(); ++i) {
headerLabels << i18n("Max Bal %1", i);
headerLabels << i18n("Max Date %1", i);
}
headerLabels << i18nc("Average balance", "Average");
m_advancedList->setHeaderLabels(headerLabels);
QTreeWidgetItem *advancedItem = 0;
QMap<QString, QString>::ConstIterator it_nc;
for (it_nc = m_nameIdx.constBegin(); it_nc != m_nameIdx.constEnd(); ++it_nc) {
const MyMoneyAccount& acc = file->account(*it_nc);
QString amount;
MyMoneyMoney amountMM;
MyMoneySecurity currency;
//change currency to deep currency if account is an investment
if (acc.isInvest()) {
MyMoneySecurity underSecurity = file->security(acc.currencyId());
currency = file->security(underSecurity.tradingCurrency());
} else {
currency = file->security(acc.currencyId());
}
advancedItem = new QTreeWidgetItem(m_advancedList, advancedItem, false);
advancedItem->setText(0, acc.name());
advancedItem->setIcon(0, acc.accountPixmap());
int it_c = 1; // iterator for the columns of the listview
//get minimum balance list
QList<QDate> minBalanceList = forecast.accountMinimumBalanceDateList(acc);
QList<QDate>::Iterator t_min;
for (t_min = minBalanceList.begin(); t_min != minBalanceList.end() ; ++t_min) {
QDate minDate = *t_min;
amountMM = forecast.forecastBalance(acc, minDate);
amount = MyMoneyUtils::formatMoney(amountMM, acc, currency);
advancedItem->setText(it_c, amount);
advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter);
if (amountMM.isNegative()) {
advancedItem->setForeground(it_c, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative));
}
it_c++;
QString dateString = QLocale().toString(minDate, QLocale::ShortFormat);
advancedItem->setText(it_c, dateString);
advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter);
if (amountMM.isNegative()) {
advancedItem->setForeground(it_c, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative));
}
it_c++;
}
//get maximum balance list
QList<QDate> maxBalanceList = forecast.accountMaximumBalanceDateList(acc);
QList<QDate>::Iterator t_max;
for (t_max = maxBalanceList.begin(); t_max != maxBalanceList.end() ; ++t_max) {
QDate maxDate = *t_max;
amountMM = forecast.forecastBalance(acc, maxDate);
amount = MyMoneyUtils::formatMoney(amountMM, acc, currency);
advancedItem->setText(it_c, amount);
advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter);
if (amountMM.isNegative()) {
advancedItem->setForeground(it_c, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative));
}
it_c++;
QString dateString = QLocale().toString(maxDate, QLocale::ShortFormat);
advancedItem->setText(it_c, dateString);
advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter);
if (amountMM.isNegative()) {
advancedItem->setForeground(it_c, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative));
}
it_c++;
}
//get average balance
amountMM = forecast.accountAverageBalance(acc);
amount = MyMoneyUtils::formatMoney(amountMM, acc, currency);
advancedItem->setText(it_c, amount);
advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter);
if (amountMM.isNegative()) {
advancedItem->setForeground(it_c, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative));
}
it_c++;
}
// make sure all data is shown
adjustHeadersAndResizeToContents(m_advancedList);
m_advancedList->show();
}
void KForecastView::loadBudgetView()
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast();
//get the settings from current page and calculate this year based on last year
QDate historyEndDate = QDate(QDate::currentDate().year() - 1, 12, 31);
QDate historyStartDate = historyEndDate.addDays(-m_accountsCycle->value() * m_forecastCycles->value());
QDate forecastStartDate = QDate(QDate::currentDate().year(), 1, 1);
QDate forecastEndDate = QDate::currentDate().addDays(m_forecastDays->value());
forecast.setHistoryMethod(m_historyMethod->checkedId());
MyMoneyBudget budget;
forecast.createBudget(budget, historyStartDate, historyEndDate, forecastStartDate, forecastEndDate, false);
m_budgetList->clear();
m_budgetList->setIconSize(QSize(22, 22));
m_budgetList->setSortingEnabled(true);
m_budgetList->sortByColumn(0, Qt::AscendingOrder);
//add columns
QStringList headerLabels;
headerLabels << i18n("Account");
{
QDate forecastStartDate = forecast.forecastStartDate();
QDate forecastEndDate = forecast.forecastEndDate();
//add cycle interval columns
QDate f_date = forecastStartDate;
for (; f_date <= forecastEndDate; f_date = f_date.addMonths(1)) {
headerLabels << QDate::longMonthName(f_date.month());
}
}
//add total column
headerLabels << i18nc("Total balance", "Total");
//set the columns
m_budgetList->setHeaderLabels(headerLabels);
//add default rows
addTotalRow(m_budgetList, forecast);
addIncomeExpenseRows(forecast);
//load income and expense budget accounts
loadAccounts(forecast, file->income(), m_incomeItem, eBudget);
loadAccounts(forecast, file->expense(), m_expenseItem, eBudget);
adjustHeadersAndResizeToContents(m_budgetList);
}
QList<MyMoneyPrice> KForecastView::getAccountPrices(const MyMoneyAccount& acc)
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyPrice> prices;
MyMoneySecurity security = file->baseCurrency();
try {
if (acc.isInvest()) {
security = file->security(acc.currencyId());
if (security.tradingCurrency() != file->baseCurrency().id()) {
MyMoneySecurity sec = file->security(security.tradingCurrency());
prices += file->price(sec.id(), file->baseCurrency().id());
}
} else if (acc.currencyId() != file->baseCurrency().id()) {
if (acc.currencyId() != file->baseCurrency().id()) {
security = file->security(acc.currencyId());
prices += file->price(acc.currencyId(), file->baseCurrency().id());
}
}
} catch (const MyMoneyException &e) {
qDebug() << Q_FUNC_INFO << " caught exception while adding " << acc.name() << "[" << acc.id() << "]: " << e.what();
}
return prices;
}
void KForecastView::addAssetLiabilityRows(const MyMoneyForecast& forecast)
{
MyMoneyFile* file = MyMoneyFile::instance();
m_assetItem = new QTreeWidgetItem(m_totalItem);
m_assetItem->setText(0, file->asset().name());
m_assetItem->setIcon(0, file->asset().accountPixmap());
m_assetItem->setData(0, ForecastRole, QVariant::fromValue(forecast));
m_assetItem->setData(0, AccountRole, QVariant::fromValue(file->asset()));
m_assetItem->setExpanded(true);
m_liabilityItem = new QTreeWidgetItem(m_totalItem);
m_liabilityItem->setText(0, file->liability().name());
m_liabilityItem->setIcon(0, file->liability().accountPixmap());
m_liabilityItem->setData(0, ForecastRole, QVariant::fromValue(forecast));
m_liabilityItem->setData(0, AccountRole, QVariant::fromValue(file->liability()));
m_liabilityItem->setExpanded(true);
}
void KForecastView::addIncomeExpenseRows(const MyMoneyForecast& forecast)
{
MyMoneyFile* file = MyMoneyFile::instance();
m_incomeItem = new QTreeWidgetItem(m_totalItem);
m_incomeItem->setText(0, file->income().name());
m_incomeItem->setIcon(0, file->income().accountPixmap());
m_incomeItem->setData(0, ForecastRole, QVariant::fromValue(forecast));
m_incomeItem->setData(0, AccountRole, QVariant::fromValue(file->income()));
m_incomeItem->setExpanded(true);
m_expenseItem = new QTreeWidgetItem(m_totalItem);
m_expenseItem->setText(0, file->expense().name());
m_expenseItem->setIcon(0, file->expense().accountPixmap());
m_expenseItem->setData(0, ForecastRole, QVariant::fromValue(forecast));
m_expenseItem->setData(0, AccountRole, QVariant::fromValue(file->expense()));
m_expenseItem->setExpanded(true);
}
void KForecastView::addTotalRow(QTreeWidget* forecastList, const MyMoneyForecast& forecast)
{
MyMoneyFile* file = MyMoneyFile::instance();
m_totalItem = new QTreeWidgetItem(forecastList);
QFont font;
font.setBold(true);
m_totalItem->setFont(0, font);
m_totalItem->setText(0, i18nc("Total balance", "Total"));
m_totalItem->setIcon(0, file->asset().accountPixmap());
m_totalItem->setData(0, ForecastRole, QVariant::fromValue(forecast));
m_totalItem->setData(0, AccountRole, QVariant::fromValue(file->asset()));
m_totalItem->setExpanded(true);
}
bool KForecastView::includeAccount(MyMoneyForecast& forecast, const MyMoneyAccount& acc)
{
MyMoneyFile* file = MyMoneyFile::instance();
if (forecast.isForecastAccount(acc))
return true;
QStringList accounts = acc.accountList();
if (accounts.size() > 0) {
QStringList::ConstIterator it_acc;
for (it_acc = accounts.constBegin(); it_acc != accounts.constEnd(); ++it_acc) {
MyMoneyAccount account = file->account(*it_acc);
if (includeAccount(forecast, account))
return true;
}
}
return false;
}
void KForecastView::adjustHeadersAndResizeToContents(QTreeWidget *widget)
{
QSize sizeHint(0, widget->sizeHintForRow(0));
QTreeWidgetItem *header = widget->headerItem();
for (int i = 0; i < header->columnCount(); ++i) {
if (i > 0) {
header->setData(i, Qt::TextAlignmentRole, Qt::AlignRight);
// make sure that the row height stays the same even when the column that has icons is not visible
if (m_totalItem) {
m_totalItem->setSizeHint(i, sizeHint);
}
}
widget->resizeColumnToContents(i);
}
}
void KForecastView::loadAccounts(MyMoneyForecast& forecast, const MyMoneyAccount& account, QTreeWidgetItem* parentItem, int forecastType)
{
QMap<QString, QString> nameIdx;
QStringList accList;
MyMoneyFile* file = MyMoneyFile::instance();
QTreeWidgetItem *forecastItem = 0;
//Get all accounts of the right type to calculate forecast
accList = account.accountList();
if (accList.size() == 0)
return;
QStringList::ConstIterator accList_t;
for (accList_t = accList.constBegin(); accList_t != accList.constEnd(); ++accList_t) {
MyMoneyAccount subAccount = file->account(*accList_t);
//only add the account if it is a forecast account or the parent of a forecast account
if (includeAccount(forecast, subAccount)) {
nameIdx[subAccount.id()] = subAccount.id();
}
}
QMap<QString, QString>::ConstIterator it_nc;
for (it_nc = nameIdx.constBegin(); it_nc != nameIdx.constEnd(); ++it_nc) {
const MyMoneyAccount subAccount = file->account(*it_nc);
MyMoneySecurity currency;
if (subAccount.isInvest()) {
MyMoneySecurity underSecurity = file->security(subAccount.currencyId());
currency = file->security(underSecurity.tradingCurrency());
} else {
currency = file->security(subAccount.currencyId());
}
forecastItem = new QTreeWidgetItem(parentItem);
forecastItem->setText(0, subAccount.name());
forecastItem->setIcon(0, subAccount.accountPixmap());
forecastItem->setData(0, ForecastRole, QVariant::fromValue(forecast));
forecastItem->setData(0, AccountRole, QVariant::fromValue(subAccount));
forecastItem->setExpanded(true);
switch (forecastType) {
case eSummary:
updateSummary(forecastItem);
break;
case eDetailed:
updateDetailed(forecastItem);
break;
case eBudget:
updateBudget(forecastItem);
break;
default:
break;
}
loadAccounts(forecast, subAccount, forecastItem, forecastType);
}
}
void KForecastView::updateSummary(QTreeWidgetItem *item)
{
MyMoneyMoney amountMM;
int it_c = 1; // iterator for the columns of the listview
MyMoneyFile* file = MyMoneyFile::instance();
int daysToBeginDay;
MyMoneyForecast forecast = item->data(0, ForecastRole).value<MyMoneyForecast>();
if (QDate::currentDate() < forecast.beginForecastDate()) {
daysToBeginDay = QDate::currentDate().daysTo(forecast.beginForecastDate());
} else {
daysToBeginDay = forecast.accountsCycle();
}
MyMoneyAccount account = item->data(0, AccountRole).value<MyMoneyAccount>();
MyMoneySecurity currency;
if (account.isInvest()) {
MyMoneySecurity underSecurity = file->security(account.currencyId());
currency = file->security(underSecurity.tradingCurrency());
} else {
currency = file->security(account.currencyId());
}
//add current balance column
QDate summaryDate = QDate::currentDate();
amountMM = forecast.forecastBalance(account, summaryDate);
//calculate the balance in base currency for the total row
setAmount(item, it_c, amountMM);
setValue(item, it_c, amountMM, summaryDate);
showAmount(item, it_c, amountMM, currency);
it_c++;
//iterate through all other columns
for (QDate summaryDate = QDate::currentDate().addDays(daysToBeginDay); summaryDate <= forecast.forecastEndDate(); summaryDate = summaryDate.addDays(forecast.accountsCycle()), ++it_c) {
amountMM = forecast.forecastBalance(account, summaryDate);
//calculate the balance in base currency for the total row
setAmount(item, it_c, amountMM);
setValue(item, it_c, amountMM, summaryDate);
showAmount(item, it_c, amountMM, currency);
}
//calculate and add variation per cycle
setNegative(item, forecast.accountTotalVariation(account).isNegative());
setAmount(item, it_c, forecast.accountTotalVariation(account));
setValue(item, it_c, forecast.accountTotalVariation(account), forecast.forecastEndDate());
showAmount(item, it_c, forecast.accountTotalVariation(account), currency);
}
void KForecastView::updateDetailed(QTreeWidgetItem *item)
{
QString amount;
QString vAmount;
MyMoneyMoney vAmountMM;
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyAccount account = item->data(0, AccountRole).value<MyMoneyAccount>();
MyMoneySecurity currency;
if (account.isInvest()) {
MyMoneySecurity underSecurity = file->security(account.currencyId());
currency = file->security(underSecurity.tradingCurrency());
} else {
currency = file->security(account.currencyId());
}
int it_c = 1; // iterator for the columns of the listview
MyMoneyForecast forecast = item->data(0, ForecastRole).value<MyMoneyForecast>();
for (QDate forecastDate = QDate::currentDate(); forecastDate <= forecast.forecastEndDate(); ++it_c, forecastDate = forecastDate.addDays(1)) {
MyMoneyMoney amountMM = forecast.forecastBalance(account, forecastDate);
//calculate the balance in base currency for the total row
setAmount(item, it_c, amountMM);
setValue(item, it_c, amountMM, forecastDate);
showAmount(item, it_c, amountMM, currency);
}
//calculate and add variation per cycle
vAmountMM = forecast.accountTotalVariation(account);
setAmount(item, it_c, vAmountMM);
setValue(item, it_c, vAmountMM, forecast.forecastEndDate());
showAmount(item, it_c, vAmountMM, currency);
}
void KForecastView::updateBudget(QTreeWidgetItem *item)
{
MyMoneySecurity currency;
MyMoneyMoney tAmountMM;
MyMoneyForecast forecast = item->data(0, ForecastRole).value<MyMoneyForecast>();
MyMoneyFile* file = MyMoneyFile::instance();
int it_c = 1; // iterator for the columns of the listview
QDate forecastDate = forecast.forecastStartDate();
MyMoneyAccount account = item->data(0, AccountRole).value<MyMoneyAccount>();
if (account.isInvest()) {
MyMoneySecurity underSecurity = file->security(account.currencyId());
currency = file->security(underSecurity.tradingCurrency());
} else {
currency = file->security(account.currencyId());
}
//iterate columns
for (; forecastDate <= forecast.forecastEndDate(); forecastDate = forecastDate.addMonths(1), ++it_c) {
MyMoneyMoney amountMM;
amountMM = forecast.forecastBalance(account, forecastDate);
- if (account.accountType() == MyMoneyAccount::Expense)
+ if (account.accountType() == eMyMoney::Account::Expense)
amountMM = -amountMM;
tAmountMM += amountMM;
setAmount(item, it_c, amountMM);
setValue(item, it_c, amountMM, forecastDate);
showAmount(item, it_c, amountMM, currency);
}
//set total column
setAmount(item, it_c, tAmountMM);
setValue(item, it_c, tAmountMM, forecast.forecastEndDate());
showAmount(item, it_c, tAmountMM, currency);
}
void KForecastView::setNegative(QTreeWidgetItem *item, bool isNegative)
{
if (isNegative) {
for (int i = 0; i < item->columnCount(); ++i) {
item->setForeground(i, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative));
}
}
}
void KForecastView::showAmount(QTreeWidgetItem* item, int column, const MyMoneyMoney& amount, const MyMoneySecurity& security)
{
item->setText(column, MyMoneyUtils::formatMoney(amount, security));
item->setTextAlignment(column, Qt::AlignRight | Qt::AlignVCenter);
item->setFont(column, item->font(0));
if (amount.isNegative()) {
item->setForeground(column, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative));
}
}
void KForecastView::adjustParentValue(QTreeWidgetItem *item, int column, const MyMoneyMoney& value)
{
if (!item)
return;
item->setData(column, ValueRole, QVariant::fromValue(item->data(column, ValueRole).value<MyMoneyMoney>() + value));
item->setData(column, ValueRole, QVariant::fromValue(item->data(column, ValueRole).value<MyMoneyMoney>().convert(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())));
// if the entry has no children,
// or it is the top entry
// or it is currently not open
// we need to display the value of it
if (item->childCount() == 0 || !item->parent() || (!item->isExpanded() && item->childCount() > 0) || (item->parent() && !item->parent()->parent())) {
if (item->childCount() > 0)
item->setText(column, " ");
MyMoneyMoney amount = item->data(column, ValueRole).value<MyMoneyMoney>();
showAmount(item, column, amount, MyMoneyFile::instance()->baseCurrency());
}
// now make sure, the upstream accounts also get notified about the value change
adjustParentValue(item->parent(), column, value);
}
void KForecastView::setValue(QTreeWidgetItem* item, int column, const MyMoneyMoney& amount, const QDate& forecastDate)
{
MyMoneyAccount account = item->data(0, AccountRole).value<MyMoneyAccount>();
//calculate the balance in base currency for the total row
if (account.currencyId() != MyMoneyFile::instance()->baseCurrency().id()) {
ReportAccount repAcc = ReportAccount(account.id());
MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(forecastDate);
MyMoneyMoney baseAmountMM = amount * curPrice;
MyMoneyMoney value = baseAmountMM.convert(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction());
item->setData(column, ValueRole, QVariant::fromValue(value));
adjustParentValue(item->parent(), column, value);
} else {
item->setData(column, ValueRole, QVariant::fromValue(item->data(column, ValueRole).value<MyMoneyMoney>() + amount));
adjustParentValue(item->parent(), column, amount);
}
}
void KForecastView::setAmount(QTreeWidgetItem* item, int column, const MyMoneyMoney& amount)
{
item->setData(column, AmountRole, QVariant::fromValue(amount));
item->setTextAlignment(column, Qt::AlignRight | Qt::AlignVCenter);
}
void KForecastView::itemExpanded(QTreeWidgetItem *item)
{
if (!item->parent() || !item->parent()->parent())
return;
for (int i = 1; i < item->columnCount(); ++i) {
showAmount(item, i, item->data(i, AmountRole).value<MyMoneyMoney>(), MyMoneyFile::instance()->security(item->data(0, AccountRole).value<MyMoneyAccount>().currencyId()));
}
}
void KForecastView::itemCollapsed(QTreeWidgetItem *item)
{
for (int i = 1; i < item->columnCount(); ++i) {
showAmount(item, i, item->data(i, ValueRole).value<MyMoneyMoney>(), MyMoneyFile::instance()->baseCurrency());
}
}
void KForecastView::loadChartView()
{
MyMoneyReport::EDetailLevel detailLevel[4] = { MyMoneyReport::eDetailAll, MyMoneyReport::eDetailTop, MyMoneyReport::eDetailGroup, MyMoneyReport::eDetailTotal };
MyMoneyReport reportCfg = MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::userDefined, // overridden by the setDateFilter() call below
detailLevel[m_comboDetail->currentIndex()],
i18n("Net Worth Forecast"),
i18n("Generated Report"));
reportCfg.setChartByDefault(true);
reportCfg.setChartCHGridLines(false);
reportCfg.setChartSVGridLines(false);
reportCfg.setChartType(MyMoneyReport::eChartLine);
reportCfg.setIncludingSchedules(false);
// FIXME: this causes a crash
//reportCfg.setColumnsAreDays( true );
reportCfg.setChartDataLabels(false);
reportCfg.setConvertCurrency(true);
reportCfg.setIncludingForecast(true);
reportCfg.setDateFilter(QDate::currentDate(), QDate::currentDate().addDays(m_forecastDays->value()));
reports::PivotTable table(reportCfg);
table.drawChart(*m_forecastChart);
// Adjust the size
m_forecastChart->resize(m_tab->width() - 10, m_tab->height());
//m_forecastChart->show();
m_forecastChart->update();
}
diff --git a/kmymoney/views/kforecastview.h b/kmymoney/views/kforecastview.h
index 42075bf16..2aa4f0206 100644
--- a/kmymoney/views/kforecastview.h
+++ b/kmymoney/views/kforecastview.h
@@ -1,186 +1,187 @@
/***************************************************************************
kforecastview.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 KFORECASTVIEW_H
#define KFORECASTVIEW_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyaccount.h"
#include "mymoneymoney.h"
#include "mymoneysecurity.h"
#include "mymoneyforecast.h"
+#include "mymoneyprice.h"
#include "ui_kforecastviewdecl.h"
namespace reports { class KReportChartView; }
class FixedColumnTreeView;
class MyMoneyForecast;
class MyMoneyPrice;
/**
* @author Alvaro Soliverez
*
* This class implements the forecast 'view'.
*/
class KForecastView : public QWidget, private Ui::KForecastViewDecl
{
Q_OBJECT
public:
enum EForecastViewType { eSummary = 0, eDetailed, eAdvanced, eBudget, eUndefined };
KForecastView(QWidget *parent = 0);
virtual ~KForecastView();
void setDefaultFocus();
void showEvent(QShowEvent* event);
public slots:
void slotLoadForecast();
void slotManualForecast();
protected:
typedef enum {
SummaryView = 0,
ListView,
AdvancedView,
BudgetView,
ChartView,
// insert new values above this line
MaxViewTabs
} ForecastViewTab;
enum ForecastViewRoles {
ForecastRole = Qt::UserRole, /**< The forecast is held in this role.*/
AccountRole = Qt::UserRole + 1, /**< The MyMoneyAccount is stored in this role in column 0.*/
AmountRole = Qt::UserRole + 2, /**< The amount.*/
ValueRole = Qt::UserRole + 3, /**< The value.*/
};
QMap<QString, QString> m_nameIdx;
/**
* This method loads the forecast view.
*/
void loadForecast(ForecastViewTab tab);
/**
* This method loads the detailed view
*/
void loadListView();
/**
* This method loads the summary view
*/
void loadSummaryView();
/**
* This method loads the advanced view
*/
void loadAdvancedView();
/**
* This method loads the budget view
*/
void loadBudgetView();
/**
* This method loads the budget view
*/
void loadChartView();
/**
* This method loads the settings from user configuration
*/
void loadForecastSettings();
protected slots:
void slotTabChanged(int index);
/**
* Get the list of prices for an account
* This is used later to create an instance of KMyMoneyAccountTreeForecastItem
*
*/
QList<MyMoneyPrice> getAccountPrices(const MyMoneyAccount& acc);
private slots:
void itemExpanded(QTreeWidgetItem *item);
void itemCollapsed(QTreeWidgetItem *item);
signals:
/**
* This signal is emitted whenever the view is about to be shown.
*/
void aboutToShow();
private:
void addAssetLiabilityRows(const MyMoneyForecast& forecast);
void addIncomeExpenseRows(const MyMoneyForecast& forecast);
void addTotalRow(QTreeWidget* forecastList, const MyMoneyForecast& forecast);
bool includeAccount(MyMoneyForecast& forecast, const MyMoneyAccount& acc);
void loadAccounts(MyMoneyForecast& forecast, const MyMoneyAccount& account, QTreeWidgetItem* parentItem, int forecastType);
void adjustHeadersAndResizeToContents(QTreeWidget *widget);
void updateSummary(QTreeWidgetItem *item);
void updateDetailed(QTreeWidgetItem *item);
void updateBudget(QTreeWidgetItem *item);
/**
* Sets the whole item to be shown with negative colors
*/
void setNegative(QTreeWidgetItem *item, bool isNegative);
void showAmount(QTreeWidgetItem *item, int column, const MyMoneyMoney &amount, const MyMoneySecurity &security);
void adjustParentValue(QTreeWidgetItem *item, int column, const MyMoneyMoney& value);
void setValue(QTreeWidgetItem *item, int column, const MyMoneyMoney &amount, const QDate &forecastDate);
void setAmount(QTreeWidgetItem *item, int column, const MyMoneyMoney &amount);
/** Initializes page and sets its load status to initialized
*/
void init();
bool m_needReload[MaxViewTabs];
/**
* This member holds the load state of page
*/
bool m_needLoad;
QTreeWidgetItem* m_totalItem;
QTreeWidgetItem* m_assetItem;
QTreeWidgetItem* m_liabilityItem;
QTreeWidgetItem* m_incomeItem;
QTreeWidgetItem* m_expenseItem;
QLayout* m_chartLayout;
reports::KReportChartView* m_forecastChart;
QScopedPointer<FixedColumnTreeView> m_fixedColumnView;
};
#endif
diff --git a/kmymoney/views/kgloballedgerview.cpp b/kmymoney/views/kgloballedgerview.cpp
index 750b2f271..44f1201f7 100644
--- a/kmymoney/views/kgloballedgerview.cpp
+++ b/kmymoney/views/kgloballedgerview.cpp
@@ -1,1641 +1,1641 @@
/***************************************************************************
kgloballedgerview.cpp - description
-------------------
begin : Wed Jul 26 2006
copyright : (C) 2006 by Thomas Baumgart
email : Thomas Baumgart <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 "kgloballedgerview.h"
#include <typeinfo>
// ----------------------------------------------------------------------------
// QT Includes
#include <QFrame>
#include <QHBoxLayout>
#include <QList>
#include <QLabel>
#include <QEvent>
#include <QVBoxLayout>
#include <QHeaderView>
#include <QApplication>
#include <QToolTip>
#include <QTimer>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KMessageBox>
#include <KToolBar>
#include <KPassivePopup>
#include <KActionCollection>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyaccount.h"
#include "mymoneyfile.h"
#include "kmymoneyaccountcombo.h"
#include "register.h"
#include "transactioneditor.h"
#include "selectedtransaction.h"
#include "kmymoneyglobalsettings.h"
#include "registersearchline.h"
#include "kfindtransactiondlg.h"
#include "kmymoney.h"
#include "scheduledtransaction.h"
#include "accountsmodel.h"
#include "models.h"
#include "mymoneyprice.h"
#include "mymoneyschedule.h"
#include "mymoneysecurity.h"
#include "mymoneytransactionfilter.h"
#include "mymoneyutils.h"
#include "transaction.h"
#include "transactionform.h"
class KGlobalLedgerView::Private
{
public:
Private();
// used to store the id of an item and the id of an immeadiate unselected sibling
void storeId(KMyMoneyRegister::RegisterItem *item, QString &id, QString &backupId) {
if (item) {
// the id of the item
id = item->id();
// the id of the item's previous/next unselected item
for (KMyMoneyRegister::RegisterItem *it = item->prevItem(); it != 0 && backupId.isEmpty(); it = it->prevItem()) {
if (!it->isSelected()) {
backupId = it->id();
}
}
// if we didn't found previous unselected items search trough the next items
for (KMyMoneyRegister::RegisterItem *it = item->nextItem(); it != 0 && backupId.isEmpty(); it = it->nextItem()) {
if (!it->isSelected()) {
backupId = it->id();
}
}
}
}
// use to match an item by it's id or a backup id which has a lower precedence
void matchItemById(KMyMoneyRegister::RegisterItem **item, KMyMoneyRegister::Transaction* t, QString &id, QString &backupId) {
if (!backupId.isEmpty() && t->id() == backupId)
*item = t;
if (!id.isEmpty() && t->id() == id) {
// we found the real thing there's no need for the backup anymore
backupId.clear();
*item = t;
}
}
MousePressFilter* m_mousePressFilter;
KMyMoneyRegister::RegisterSearchLineWidget* m_registerSearchLine;
QString m_reconciliationAccount;
QDate m_reconciliationDate;
MyMoneyMoney m_endingBalance;
int m_precision;
bool m_recursion;
bool m_showDetails;
KMyMoneyRegister::Action m_action;
// models
AccountNamesFilterProxyModel *m_filterProxyModel;
// widgets
KMyMoneyAccountCombo* m_accountComboBox;
MyMoneyMoney m_totalBalance;
bool m_balanceIsApproximated;
};
MousePressFilter::MousePressFilter(QWidget* parent) :
QObject(parent),
m_lastMousePressEvent(0),
m_filterActive(true)
{
}
void MousePressFilter::addWidget(QWidget* w)
{
m_parents.append(w);
}
void MousePressFilter::setFilterActive(bool state)
{
m_filterActive = state;
}
bool MousePressFilter::isChildOf(QWidget* child, QWidget *parent)
{
// QDialogs cannot be detected directly, but it can be assumed,
// that events on a widget that do not have a parent widget within
// our application are dialogs.
if (!child->parentWidget())
return true;
while (child) {
if (child == parent)
return true;
// If one of the ancestors is a KPassivePopup or a KDialog or a popup widget then
// it's as if it is a child of our own because these widgets could
// appear during transaction entry (message boxes, completer widgets)
if (dynamic_cast<KPassivePopup*>(child) ||
((child->windowFlags() & Qt::Popup) && child != kmymoney))
return true;
child = child->parentWidget();
}
return false;
}
bool MousePressFilter::eventFilter(QObject* o, QEvent* e)
{
if (m_filterActive) {
if (e->type() == QEvent::MouseButtonPress && !m_lastMousePressEvent) {
QList<QWidget*>::const_iterator it_w;
for (it_w = m_parents.constBegin(); it_w != m_parents.constEnd(); ++it_w) {
if (isChildOf((QWidget*)o, (*it_w))) {
m_lastMousePressEvent = e;
break;
}
}
if (it_w == m_parents.constEnd()) {
m_lastMousePressEvent = e;
bool rc = false;
emit mousePressedOnExternalWidget(rc);
}
}
if (e->type() != QEvent::MouseButtonPress) {
m_lastMousePressEvent = 0;
}
}
return false;
}
KGlobalLedgerView::Private::Private() :
m_mousePressFilter(0),
m_registerSearchLine(0),
m_recursion(false),
m_showDetails(false),
m_filterProxyModel(0),
m_accountComboBox(0),
m_balanceIsApproximated(false)
{
}
QDate KGlobalLedgerView::m_lastPostDate;
KGlobalLedgerView::KGlobalLedgerView(QWidget *parent)
: KMyMoneyViewBase(parent),
d(new Private),
m_needReload(false),
m_needLoad(true),
m_newAccountLoaded(true),
m_inEditMode(false)
{
}
KGlobalLedgerView::~KGlobalLedgerView()
{
delete d;
}
void KGlobalLedgerView::setDefaultFocus()
{
QTimer::singleShot(0, d->m_registerSearchLine->searchLine(), SLOT(setFocus()));
}
void KGlobalLedgerView::init()
{
m_needLoad = false;
auto vbox = new QVBoxLayout(this);
setLayout(vbox);
vbox->setSpacing(6);
vbox->setMargin(0);
d->m_mousePressFilter = new MousePressFilter((QWidget*)this);
d->m_action = KMyMoneyRegister::ActionNone;
// the proxy filter model
d->m_filterProxyModel = new AccountNamesFilterProxyModel(this);
- d->m_filterProxyModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Asset, MyMoneyAccount::Liability, MyMoneyAccount::Equity});
+ d->m_filterProxyModel->addAccountGroup(QVector<eMyMoney::Account> {eMyMoney::Account::Asset, eMyMoney::Account::Liability, eMyMoney::Account::Equity});
auto const model = Models::instance()->accountsModel();
d->m_filterProxyModel->setSourceModel(model);
d->m_filterProxyModel->setSourceColumns(model->getColumns());
d->m_filterProxyModel->sort((int)eAccountsModel::Column::Account);
// create the toolbar frame at the top of the view
m_toolbarFrame = new QFrame();
QHBoxLayout* toolbarLayout = new QHBoxLayout(m_toolbarFrame);
toolbarLayout->setContentsMargins(0, 0, 0, 0);
toolbarLayout->setSpacing(6);
// the account selector widget
d->m_accountComboBox = new KMyMoneyAccountCombo();
d->m_accountComboBox->setModel(d->m_filterProxyModel);
toolbarLayout->addWidget(d->m_accountComboBox);
vbox->addWidget(m_toolbarFrame);
toolbarLayout->setStretchFactor(d->m_accountComboBox, 60);
// create the register frame
m_registerFrame = new QFrame();
QVBoxLayout* registerFrameLayout = new QVBoxLayout(m_registerFrame);
registerFrameLayout->setContentsMargins(0, 0, 0, 0);
registerFrameLayout->setSpacing(0);
vbox->addWidget(m_registerFrame);
vbox->setStretchFactor(m_registerFrame, 2);
m_register = new KMyMoneyRegister::Register(m_registerFrame);
m_register->setUsedWithEditor(true);
registerFrameLayout->addWidget(m_register);
m_register->installEventFilter(this);
connect(m_register, SIGNAL(openContextMenu()), this, SIGNAL(openContextMenu()));
connect(m_register, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotUpdateSummaryLine(KMyMoneyRegister::SelectedTransactions)));
connect(m_register->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotSortOptions()));
connect(m_register, SIGNAL(reconcileStateColumnClicked(KMyMoneyRegister::Transaction*)), this, SLOT(slotToggleTransactionMark(KMyMoneyRegister::Transaction*)));
// insert search line widget
d->m_registerSearchLine = new KMyMoneyRegister::RegisterSearchLineWidget(m_register, m_toolbarFrame);
toolbarLayout->addWidget(d->m_registerSearchLine);
toolbarLayout->setStretchFactor(d->m_registerSearchLine, 100);
// create the summary frame
m_summaryFrame = new QFrame();
QHBoxLayout* summaryFrameLayout = new QHBoxLayout(m_summaryFrame);
summaryFrameLayout->setContentsMargins(0, 0, 0, 0);
summaryFrameLayout->setSpacing(0);
m_leftSummaryLabel = new QLabel(m_summaryFrame);
m_centerSummaryLabel = new QLabel(m_summaryFrame);
m_rightSummaryLabel = new QLabel(m_summaryFrame);
summaryFrameLayout->addWidget(m_leftSummaryLabel);
QSpacerItem* spacer = new QSpacerItem(20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
summaryFrameLayout->addItem(spacer);
summaryFrameLayout->addWidget(m_centerSummaryLabel);
spacer = new QSpacerItem(20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
summaryFrameLayout->addItem(spacer);
summaryFrameLayout->addWidget(m_rightSummaryLabel);
vbox->addWidget(m_summaryFrame);
// create the button frame
m_buttonFrame = new QFrame(this);
QVBoxLayout* buttonLayout = new QVBoxLayout(m_buttonFrame);
buttonLayout->setContentsMargins(0, 0, 0, 0);
buttonLayout->setSpacing(0);
vbox->addWidget(m_buttonFrame);
m_buttonbar = new KToolBar(m_buttonFrame, 0, true);
m_buttonbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
buttonLayout->addWidget(m_buttonbar);
m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionNew]));
m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionDelete]));
m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionEdit]));
m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionEnter]));
m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionCancel]));
m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionAccept]));
m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionMatch]));
// create the transaction form frame
m_formFrame = new QFrame(this);
QVBoxLayout* frameLayout = new QVBoxLayout(m_formFrame);
frameLayout->setContentsMargins(5, 5, 5, 5);
frameLayout->setSpacing(0);
m_form = new KMyMoneyTransactionForm::TransactionForm(m_formFrame);
frameLayout->addWidget(m_form->tabBar(m_formFrame));
frameLayout->addWidget(m_form);
m_formFrame->setFrameShape(QFrame::Panel);
m_formFrame->setFrameShadow(QFrame::Raised);
vbox->addWidget(m_formFrame);
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView()));
connect(m_register, SIGNAL(focusChanged(KMyMoneyRegister::Transaction*)), m_form, SLOT(slotSetTransaction(KMyMoneyRegister::Transaction*)));
connect(m_register, SIGNAL(focusChanged()), kmymoney, SLOT(slotUpdateActions()));
connect(d->m_accountComboBox, SIGNAL(accountSelected(QString)), this, SLOT(slotSelectAccount(QString)));
connect(m_register, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)));
connect(m_register, SIGNAL(editTransaction()), this, SIGNAL(startEdit()));
connect(m_register, SIGNAL(emptyItemSelected()), this, SLOT(slotNewTransaction()));
connect(m_register, SIGNAL(aboutToSelectItem(KMyMoneyRegister::RegisterItem*,bool&)), this, SLOT(slotAboutToSelectItem(KMyMoneyRegister::RegisterItem*,bool&)));
connect(d->m_mousePressFilter, SIGNAL(mousePressedOnExternalWidget(bool&)), this, SIGNAL(cancelOrEndEdit(bool&)));
connect(m_form, SIGNAL(newTransaction(KMyMoneyRegister::Action)), this, SLOT(slotNewTransaction(KMyMoneyRegister::Action)));
// setup mouse press filter
d->m_mousePressFilter->addWidget(m_formFrame);
d->m_mousePressFilter->addWidget(m_buttonFrame);
d->m_mousePressFilter->addWidget(m_summaryFrame);
d->m_mousePressFilter->addWidget(m_registerFrame);
m_tooltipPosn = QPoint();
}
void KGlobalLedgerView::slotAboutToSelectItem(KMyMoneyRegister::RegisterItem* item, bool& okToSelect)
{
Q_UNUSED(item);
emit cancelOrEndEdit(okToSelect);
}
void KGlobalLedgerView::slotLoadView()
{
m_needReload = true;
if (isVisible()) {
if (!m_inEditMode) {
setUpdatesEnabled(false);
loadView();
setUpdatesEnabled(true);
m_needReload = false;
// force a new account if the current one is empty
m_newAccountLoaded = m_account.id().isEmpty();
}
}
}
void KGlobalLedgerView::clear()
{
// clear current register contents
m_register->clear();
// setup header font
QFont font = KMyMoneyGlobalSettings::listHeaderFont();
QFontMetrics fm(font);
int height = fm.lineSpacing() + 6;
m_register->horizontalHeader()->setMinimumHeight(height);
m_register->horizontalHeader()->setMaximumHeight(height);
m_register->horizontalHeader()->setFont(font);
// setup cell font
font = KMyMoneyGlobalSettings::listCellFont();
m_register->setFont(font);
// clear the form
m_form->clear();
// the selected transactions list
m_transactionList.clear();
// and the selected account in the combo box
d->m_accountComboBox->setSelected(QString());
// fraction defaults to two digits
d->m_precision = 2;
}
void KGlobalLedgerView::loadView()
{
MYMONEYTRACER(tracer);
// setup form visibility
m_formFrame->setVisible(KMyMoneyGlobalSettings::transactionForm());
// no account selected
emit accountSelected(MyMoneyAccount());
// no transaction selected
KMyMoneyRegister::SelectedTransactions list;
emit transactionsSelected(list);
QMap<QString, bool> isSelected;
QString focusItemId;
QString backUpFocusItemId; // in case the focus item is removed
QString anchorItemId;
QString backUpAnchorItemId; // in case the anchor item is removed
if (!m_newAccountLoaded) {
// remember the current selected transactions
KMyMoneyRegister::RegisterItem* item = m_register->firstItem();
for (; item; item = item->nextItem()) {
if (item->isSelected()) {
isSelected[item->id()] = true;
}
}
// remember the item that has the focus
d->storeId(m_register->focusItem(), focusItemId, backUpFocusItemId);
// and the one that has the selection anchor
d->storeId(m_register->anchorItem(), anchorItemId, backUpAnchorItemId);
} else {
d->m_registerSearchLine->searchLine()->reset();
}
// clear the current contents ...
clear();
// ... load the combobox widget and select current account ...
loadAccounts();
// ... setup the register columns ...
m_register->setupRegister(m_account);
// ... setup the form ...
m_form->setupForm(m_account);
if (m_account.id().isEmpty()) {
// if we don't have an account we bail out
setEnabled(false);
return;
}
setEnabled(true);
m_register->setUpdatesEnabled(false);
// ... and recreate it
KMyMoneyRegister::RegisterItem* focusItem = 0;
KMyMoneyRegister::RegisterItem* anchorItem = 0;
QMap<QString, MyMoneyMoney> actBalance, clearedBalance, futureBalance;
QMap<QString, MyMoneyMoney>::iterator it_b;
try {
// setup the filter to select the transactions we want to display
// and update the sort order
QString sortOrder;
QString key;
QDate reconciliationDate = d->m_reconciliationDate;
MyMoneyTransactionFilter filter(m_account.id());
// if it's an investment account, we also take care of
// the sub-accounts (stock accounts)
- if (m_account.accountType() == MyMoneyAccount::Investment)
+ if (m_account.accountType() == eMyMoney::Account::Investment)
filter.addAccount(m_account.accountList());
if (isReconciliationAccount()) {
key = "kmm-sort-reconcile";
sortOrder = KMyMoneyGlobalSettings::sortReconcileView();
- filter.addState(MyMoneyTransactionFilter::notReconciled);
- filter.addState(MyMoneyTransactionFilter::cleared);
+ filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled);
+ filter.addState((int)eMyMoney::TransactionFilter::State::Cleared);
} else {
filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate());
key = "kmm-sort-std";
sortOrder = KMyMoneyGlobalSettings::sortNormalView();
if (KMyMoneyGlobalSettings::hideReconciledTransactions()
&& !m_account.isIncomeExpense()) {
- filter.addState(MyMoneyTransactionFilter::notReconciled);
- filter.addState(MyMoneyTransactionFilter::cleared);
+ filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled);
+ filter.addState((int)eMyMoney::TransactionFilter::State::Cleared);
}
}
filter.setReportAllSplits(true);
// check if we have an account override of the sort order
if (!m_account.value(key).isEmpty())
sortOrder = m_account.value(key);
// setup sort order
m_register->setSortOrder(sortOrder);
// retrieve the list from the engine
MyMoneyFile::instance()->transactionList(m_transactionList, filter);
kmymoney->slotStatusProgressBar(0, m_transactionList.count());
// create the elements for the register
QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
QMap<QString, int>uniqueMap;
int i = 0;
for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) {
uniqueMap[(*it).first.id()]++;
KMyMoneyRegister::Transaction* t = KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]);
actBalance[t->split().accountId()] = MyMoneyMoney();
kmymoney->slotStatusProgressBar(++i, 0);
// if we're in reconciliation and the state is cleared, we
// force the item to show in dimmed intensity to get a visual focus
// on those items, that we need to work on
if (isReconciliationAccount() && (*it).second.reconcileFlag() == MyMoneySplit::Cleared) {
t->setReducedIntensity(true);
}
}
// create dummy entries for the scheduled transactions if sorted by postdate
int period = KMyMoneyGlobalSettings::schedulePreview();
if (m_register->primarySortKey() == KMyMoneyRegister::PostDateSort) {
// show scheduled transactions which have a scheduled postdate
// within the next 'period' days. In reconciliation mode, the
// period starts on the statement date.
QDate endDate = QDate::currentDate().addDays(period);
if (isReconciliationAccount())
endDate = reconciliationDate.addDays(period);
QList<MyMoneySchedule> scheduleList = MyMoneyFile::instance()->scheduleList(m_account.id());
while (scheduleList.count() > 0) {
MyMoneySchedule& s = scheduleList.first();
for (;;) {
if (s.isFinished() || s.adjustedNextDueDate() > endDate) {
break;
}
MyMoneyTransaction t(s.id(), KMyMoneyUtils::scheduledTransaction(s));
// if the transaction is scheduled and overdue, it can't
// certainly be posted in the past. So we take today's date
// as the alternative
if (s.isOverdue()) {
t.setPostDate(s.adjustedDate(QDate::currentDate(), s.weekendOption()));
} else {
t.setPostDate(s.adjustedNextDueDate());
}
const QList<MyMoneySplit>& splits = t.splits();
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = splits.begin(); it_s != splits.end(); ++it_s) {
if ((*it_s).accountId() == m_account.id()) {
new KMyMoneyRegister::StdTransactionScheduled(m_register, t, *it_s, uniqueMap[t.id()]);
}
}
// keep track of this payment locally (not in the engine)
if (s.isOverdue()) {
s.setLastPayment(QDate::currentDate());
} else {
s.setLastPayment(s.nextDueDate());
}
// if this is a one time schedule, we can bail out here as we're done
- if (s.occurrence() == MyMoneySchedule::OCCUR_ONCE)
+ if (s.occurrence() == eMyMoney::Schedule::Occurrence::Once)
break;
// for all others, we check if the next payment date is still 'in range'
QDate nextDueDate = s.nextPayment(s.nextDueDate());
if (nextDueDate.isValid()) {
s.setNextDueDate(nextDueDate);
} else {
break;
}
}
scheduleList.pop_front();
}
}
// add the group markers
m_register->addGroupMarkers();
// sort the transactions according to the sort setting
m_register->sortItems();
// remove trailing and adjacent markers
m_register->removeUnwantedGroupMarkers();
// add special markers for reconciliation now so that they do not get
// removed by m_register->removeUnwantedGroupMarkers(). Needs resorting
// of items but that's ok.
KMyMoneyRegister::StatementGroupMarker* statement = 0;
KMyMoneyRegister::StatementGroupMarker* dStatement = 0;
KMyMoneyRegister::StatementGroupMarker* pStatement = 0;
if (isReconciliationAccount()) {
switch (m_register->primarySortKey()) {
case KMyMoneyRegister::PostDateSort:
statement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Deposit, reconciliationDate, i18n("Statement Details"));
m_register->sortItems();
break;
case KMyMoneyRegister::TypeSort:
dStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Deposit, reconciliationDate, i18n("Statement Deposit Details"));
pStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Payment, reconciliationDate, i18n("Statement Payment Details"));
m_register->sortItems();
break;
default:
break;
}
}
// we need at least the balance for the account we currently show
actBalance[m_account.id()] = MyMoneyMoney();
- if (m_account.accountType() == MyMoneyAccount::Investment) {
+ if (m_account.accountType() == eMyMoney::Account::Investment) {
QList<QString>::const_iterator it_a;
for (it_a = m_account.accountList().begin(); it_a != m_account.accountList().end(); ++it_a) {
actBalance[*it_a] = MyMoneyMoney();
}
}
// determine balances (actual, cleared). We do this by getting the actual
// balance of all entered transactions from the engine and walk the list
// of transactions backward. Also re-select a transaction if it was
// selected before and setup the focus item.
MyMoneyMoney factor(1, 1);
- if (m_account.accountGroup() == MyMoneyAccount::Liability
- || m_account.accountGroup() == MyMoneyAccount::Equity)
+ if (m_account.accountGroup() == eMyMoney::Account::Liability
+ || m_account.accountGroup() == eMyMoney::Account::Equity)
factor = -factor;
QMap<QString, int> deposits;
QMap<QString, int> payments;
QMap<QString, MyMoneyMoney> depositAmount;
QMap<QString, MyMoneyMoney> paymentAmount;
for (it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) {
MyMoneyMoney balance = MyMoneyFile::instance()->balance(it_b.key());
balance = balance * factor;
clearedBalance[it_b.key()] =
futureBalance[it_b.key()] =
(*it_b) = balance;
deposits[it_b.key()] = payments[it_b.key()] = 0;
depositAmount[it_b.key()] = MyMoneyMoney();
paymentAmount[it_b.key()] = MyMoneyMoney();
}
tracer.printf("total balance of %s = %s", qPrintable(m_account.name()), qPrintable(actBalance[m_account.id()].formatMoney("", 2)));
tracer.printf("future balance of %s = %s", qPrintable(m_account.name()), qPrintable(futureBalance[m_account.id()].formatMoney("", 2)));
tracer.printf("cleared balance of %s = %s", qPrintable(m_account.name()), qPrintable(clearedBalance[m_account.id()].formatMoney("", 2)));
KMyMoneyRegister::RegisterItem* p = m_register->lastItem();
focusItem = 0;
// take care of possibly trailing scheduled transactions (bump up the future balance)
while (p) {
if (p->isSelectable()) {
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
if (t && t->isScheduled()) {
MyMoneyMoney balance = futureBalance[t->split().accountId()];
const MyMoneySplit& split = t->split();
// if this split is a stock split, we can't just add the amount of shares
if (t->transaction().isStockSplit()) {
balance = balance * split.shares();
} else {
balance += split.shares() * factor;
}
futureBalance[split.accountId()] = balance;
} else if (t && !focusItem)
focusItem = p;
}
p = p->prevItem();
}
p = m_register->lastItem();
while (p) {
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
if (t) {
if (isSelected.contains(t->id()))
t->setSelected(true);
d->matchItemById(&focusItem, t, focusItemId, backUpFocusItemId);
d->matchItemById(&anchorItem, t, anchorItemId, backUpAnchorItemId);
const MyMoneySplit& split = t->split();
MyMoneyMoney balance = futureBalance[split.accountId()];
t->setBalance(balance);
// if this split is a stock split, we can't just add the amount of shares
if (t->transaction().isStockSplit()) {
balance /= split.shares();
} else {
balance -= split.shares() * factor;
}
if (!t->isScheduled()) {
if (isReconciliationAccount() && t->transaction().postDate() <= reconciliationDate && split.reconcileFlag() == MyMoneySplit::Cleared) {
if (split.shares().isNegative()) {
payments[split.accountId()]++;
paymentAmount[split.accountId()] += split.shares();
} else {
deposits[split.accountId()]++;
depositAmount[split.accountId()] += split.shares();
}
}
if (t->transaction().postDate() > QDate::currentDate()) {
tracer.printf("Reducing actual balance by %s because %s/%s(%s) is in the future", qPrintable((split.shares() * factor).formatMoney("", 2)), qPrintable(t->transaction().id()), qPrintable(split.id()), qPrintable(t->transaction().postDate().toString(Qt::ISODate)));
actBalance[split.accountId()] -= split.shares() * factor;
}
}
futureBalance[split.accountId()] = balance;
}
p = p->prevItem();
}
clearedBalance[m_account.id()] = MyMoneyFile::instance()->clearedBalance(m_account.id(), reconciliationDate);
tracer.printf("total balance of %s = %s", qPrintable(m_account.name()), qPrintable(actBalance[m_account.id()].formatMoney("", 2)));
tracer.printf("future balance of %s = %s", qPrintable(m_account.name()), qPrintable(futureBalance[m_account.id()].formatMoney("", 2)));
tracer.printf("cleared balance of %s = %s", qPrintable(m_account.name()), qPrintable(clearedBalance[m_account.id()].formatMoney("", 2)));
// update statement information
if (statement) {
const QString aboutDeposits = i18np("%1 deposit (%2)", "%1 deposits (%2)",
deposits[m_account.id()], depositAmount[m_account.id()].abs().formatMoney(m_account.fraction()));
const QString aboutPayments = i18np("%1 payment (%2)", "%1 payments (%2)",
payments[m_account.id()], paymentAmount[m_account.id()].abs().formatMoney(m_account.fraction()));
statement->setText(i18nc("%1 is a string, e.g. 7 deposits; %2 is a string, e.g. 4 payments", "%1, %2", aboutDeposits, aboutPayments));
}
if (pStatement) {
pStatement->setText(i18np("%1 payment (%2)", "%1 payments (%2)", payments[m_account.id()]
, paymentAmount[m_account.id()].abs().formatMoney(m_account.fraction())));
}
if (dStatement) {
dStatement->setText(i18np("%1 deposit (%2)", "%1 deposits (%2)", deposits[m_account.id()]
, depositAmount[m_account.id()].abs().formatMoney(m_account.fraction())));
}
// add a last empty entry for new transactions
// leave some information about the current account
MyMoneySplit split;
split.setReconcileFlag(MyMoneySplit::NotReconciled);
// make sure to use the value specified in the option during reconciliation
if (isReconciliationAccount())
split.setReconcileFlag(static_cast<MyMoneySplit::reconcileFlagE>(KMyMoneyGlobalSettings::defaultReconciliationState()));
KMyMoneyRegister::Register::transactionFactory(m_register, MyMoneyTransaction(), split, 0);
m_register->updateRegister(true);
if (focusItem) {
// in case we have some selected items we just set the focus item
// in other cases, we make the focusitem also the selected item
if (anchorItem && (anchorItem != focusItem)) {
m_register->setFocusItem(focusItem);
m_register->setAnchorItem(anchorItem);
} else
m_register->selectItem(focusItem, true);
} else {
// just use the empty line at the end if nothing else exists in the ledger
p = m_register->lastItem();
m_register->setFocusItem(p);
m_register->selectItem(p);
focusItem = p;
}
updateSummaryLine(actBalance, clearedBalance);
kmymoney->slotStatusProgressBar(-1, -1);
} catch (const MyMoneyException &) {
m_account = MyMoneyAccount();
clear();
}
d->m_showDetails = KMyMoneyGlobalSettings::showRegisterDetailed();
// and tell everyone what's selected
emit accountSelected(m_account);
KMyMoneyRegister::SelectedTransactions actualSelection(m_register);
emit transactionsSelected(actualSelection);
}
void KGlobalLedgerView::updateSummaryLine(const QMap<QString, MyMoneyMoney>& actBalance, const QMap<QString, MyMoneyMoney>& clearedBalance)
{
MyMoneyFile* file = MyMoneyFile::instance();
m_leftSummaryLabel->show();
m_centerSummaryLabel->show();
m_rightSummaryLabel->show();
if (isReconciliationAccount()) {
- if (m_account.accountType() != MyMoneyAccount::Investment) {
+ if (m_account.accountType() != eMyMoney::Account::Investment) {
m_leftSummaryLabel->setText(i18n("Statement: %1", d->m_endingBalance.formatMoney("", d->m_precision)));
m_centerSummaryLabel->setText(i18nc("Cleared balance", "Cleared: %1", clearedBalance[m_account.id()].formatMoney("", d->m_precision)));
d->m_totalBalance = clearedBalance[m_account.id()] - d->m_endingBalance;
}
} else {
// update summary line in normal mode
QDate reconcileDate = m_account.lastReconciliationDate();
if (reconcileDate.isValid()) {
m_leftSummaryLabel->setText(i18n("Last reconciled: %1", QLocale().toString(reconcileDate, QLocale::ShortFormat)));
} else {
m_leftSummaryLabel->setText(i18n("Never reconciled"));
}
QPalette palette = m_rightSummaryLabel->palette();
palette.setColor(m_rightSummaryLabel->foregroundRole(), m_leftSummaryLabel->palette().color(foregroundRole()));
- if (m_account.accountType() != MyMoneyAccount::Investment) {
+ if (m_account.accountType() != eMyMoney::Account::Investment) {
m_centerSummaryLabel->setText(i18nc("Cleared balance", "Cleared: %1", clearedBalance[m_account.id()].formatMoney("", d->m_precision)));
d->m_totalBalance = actBalance[m_account.id()];
} else {
m_centerSummaryLabel->hide();
MyMoneyMoney balance;
MyMoneySecurity base = file->baseCurrency();
QMap<QString, MyMoneyMoney>::const_iterator it_b;
// reset the approximated flag
d->m_balanceIsApproximated = false;
for (it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) {
MyMoneyAccount stock = file->account(it_b.key());
QString currencyId = stock.currencyId();
MyMoneySecurity sec = file->security(currencyId);
MyMoneyMoney rate(1, 1);
if (stock.isInvest()) {
currencyId = sec.tradingCurrency();
const MyMoneyPrice &priceInfo = file->price(sec.id(), currencyId);
d->m_balanceIsApproximated |= !priceInfo.isValid();
rate = priceInfo.rate(sec.tradingCurrency());
}
if (currencyId != base.id()) {
const MyMoneyPrice &priceInfo = file->price(sec.tradingCurrency(), base.id());
d->m_balanceIsApproximated |= !priceInfo.isValid();
rate = (rate * priceInfo.rate(base.id())).convertPrecision(sec.pricePrecision());
}
balance += ((*it_b) * rate).convert(base.smallestAccountFraction());
}
d->m_totalBalance = balance;
}
m_rightSummaryLabel->setPalette(palette);
}
// determine the number of selected transactions
KMyMoneyRegister::SelectedTransactions selection;
m_register->selectedTransactions(selection);
slotUpdateSummaryLine(selection);
}
void KGlobalLedgerView::slotUpdateSummaryLine(const KMyMoneyRegister::SelectedTransactions& selection)
{
if (selection.count() > 1) {
MyMoneyMoney balance;
foreach (const KMyMoneyRegister::SelectedTransaction& t, selection) {
if (!t.isScheduled()) {
balance += t.split().shares();
}
}
m_rightSummaryLabel->setText(QString("%1: %2").arg(QChar(0x2211), balance.formatMoney("", d->m_precision)));
} else {
if (isReconciliationAccount()) {
m_rightSummaryLabel->setText(i18n("Difference: %1", d->m_totalBalance.formatMoney("", d->m_precision)));
} else {
- if (m_account.accountType() != MyMoneyAccount::Investment) {
+ if (m_account.accountType() != eMyMoney::Account::Investment) {
m_rightSummaryLabel->setText(i18n("Balance: %1", d->m_totalBalance.formatMoney("", d->m_precision)));
bool showNegative = d->m_totalBalance.isNegative();
- if (m_account.accountGroup() == MyMoneyAccount::Liability && !d->m_totalBalance.isZero())
+ if (m_account.accountGroup() == eMyMoney::Account::Liability && !d->m_totalBalance.isZero())
showNegative = !showNegative;
if (showNegative) {
QPalette palette = m_rightSummaryLabel->palette();
palette.setColor(m_rightSummaryLabel->foregroundRole(), KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative));
m_rightSummaryLabel->setPalette(palette);
}
} else {
m_rightSummaryLabel->setText(i18n("Investment value: %1%2",
d->m_balanceIsApproximated ? "~" : "",
d->m_totalBalance.formatMoney(MyMoneyFile::instance()->baseCurrency().tradingSymbol(), d->m_precision)));
}
}
}
}
void KGlobalLedgerView::resizeEvent(QResizeEvent* ev)
{
if (m_needLoad)
init();
m_register->resize(KMyMoneyRegister::DetailColumn);
m_form->resize(KMyMoneyTransactionForm::ValueColumn1);
KMyMoneyViewBase::resizeEvent(ev);
}
void KGlobalLedgerView::loadAccounts()
{
MyMoneyFile* file = MyMoneyFile::instance();
auto const model = Models::instance()->accountsModel();
// check if the current account still exists and make it the
// current account
if (!m_account.id().isEmpty()) {
try {
m_account = file->account(m_account.id());
} catch (const MyMoneyException &) {
m_account = MyMoneyAccount();
return;
}
}
// TODO: check why the invalidate is needed here
d->m_filterProxyModel->invalidate();
d->m_filterProxyModel->sort((int)eAccountsModel::Column::Account);
d->m_filterProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !kmymoney->isActionToggled(Action::ViewShowAll));
d->m_filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode());
d->m_accountComboBox->expandAll();
if (m_account.id().isEmpty()) {
// find the first favorite account
QModelIndexList list = model->match(model->index(0, 0),
(int)eAccountsModel::Role::Favorite,
QVariant(true),
1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
if (list.count() > 0) {
QVariant accountId = list.front().data((int)eAccountsModel::Role::ID);
if (accountId.isValid()) {
m_account = file->account(accountId.toString());
}
}
if (m_account.id().isEmpty()) {
// there are no favorite accounts find any account
QModelIndexList list = model->match(model->index(0, 0),
Qt::DisplayRole,
QVariant(QString("*")),
-1,
Qt::MatchFlags(Qt::MatchWildcard | Qt::MatchRecursive));
for (QModelIndexList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) {
if (!it->parent().isValid())
continue; // skip the top level accounts
QVariant accountId = (*it).data((int)eAccountsModel::Role::ID);
if (accountId.isValid()) {
MyMoneyAccount a = file->account(accountId.toString());
if (!a.isInvest()) {
m_account = a;
break;
}
}
}
}
}
if (!m_account.id().isEmpty()) {
d->m_accountComboBox->setSelected(m_account.id());
try {
d->m_precision = MyMoneyMoney::denomToPrec(m_account.fraction());
} catch (const MyMoneyException &) {
qDebug("Security %s for account %s not found", qPrintable(m_account.currencyId()), qPrintable(m_account.name()));
d->m_precision = 2;
}
}
}
void KGlobalLedgerView::selectTransaction(const QString& id)
{
if (!id.isEmpty()) {
KMyMoneyRegister::RegisterItem* p = m_register->lastItem();
while (p) {
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
if (t) {
if (t->transaction().id() == id) {
m_register->selectItem(t);
m_register->ensureItemVisible(t);
break;
}
}
p = p->prevItem();
}
}
}
void KGlobalLedgerView::slotSelectAllTransactions()
{
if(m_needLoad)
init();
m_register->clearSelection();
KMyMoneyRegister::RegisterItem* p = m_register->firstItem();
while (p) {
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
if (t) {
if (t->isVisible() && t->isSelectable() && !t->isScheduled() && !t->id().isEmpty()) {
t->setSelected(true);
}
}
p = p->nextItem();
}
// this is here only to re-paint the items without selecting anything because the data (including the selection) is not really held in the model right now
m_register->selectAll();
// inform everyone else about the selected items
KMyMoneyRegister::SelectedTransactions list(m_register);
emit transactionsSelected(list);
}
void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance)
{
if(m_needLoad)
init();
if (d->m_reconciliationAccount != acc.id()) {
// make sure the account is selected
if (!acc.id().isEmpty())
slotSelectAccount(acc.id());
d->m_reconciliationAccount = acc.id();
d->m_reconciliationDate = reconciliationDate;
d->m_endingBalance = endingBalance;
- if (acc.accountGroup() == MyMoneyAccount::Liability)
+ if (acc.accountGroup() == eMyMoney::Account::Liability)
d->m_endingBalance = -endingBalance;
m_newAccountLoaded = true;
if (acc.id().isEmpty()) {
m_buttonbar->removeAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::AccountPostponeReconciliation]));
m_buttonbar->removeAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::AccountFinishReconciliation]));
} else {
m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::AccountPostponeReconciliation]));
m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::AccountFinishReconciliation]));
// when we start reconciliation, we need to reload the view
// because no data has been changed. When postponing or finishing
// reconciliation, the data change in the engine takes care of updateing
// the view.
slotLoadView();
}
}
}
bool KGlobalLedgerView::isReconciliationAccount() const
{
return m_account.id() == d->m_reconciliationAccount;
}
bool KGlobalLedgerView::slotSelectAccount(const MyMoneyObject& obj)
{
if (typeid(obj) != typeid(MyMoneyAccount))
return false;
if (d->m_recursion)
return false;
d->m_recursion = true;
const MyMoneyAccount& acc = dynamic_cast<const MyMoneyAccount&>(obj);
bool rc = slotSelectAccount(acc.id());
d->m_recursion = false;
return rc;
}
bool KGlobalLedgerView::slotSelectAccount(const QString& id, const QString& transactionId)
{
bool rc = true;
if (!id.isEmpty()) {
if (m_account.id() != id) {
try {
m_account = MyMoneyFile::instance()->account(id);
// if a stock account is selected, we show the
// the corresponding parent (investment) account
if (m_account.isInvest()) {
m_account = MyMoneyFile::instance()->account(m_account.parentAccountId());
}
m_newAccountLoaded = true;
slotLoadView();
} catch (const MyMoneyException &) {
qDebug("Unable to retrieve account %s", qPrintable(id));
rc = false;
}
} else {
// we need to refresh m_account.m_accountList, a child could have been deleted
m_account = MyMoneyFile::instance()->account(id);
emit accountSelected(m_account);
}
selectTransaction(transactionId);
}
return rc;
}
void KGlobalLedgerView::slotNewTransaction(KMyMoneyRegister::Action id)
{
if (!m_inEditMode) {
d->m_action = id;
emit newTransaction();
}
}
void KGlobalLedgerView::slotNewTransaction()
{
slotNewTransaction(KMyMoneyRegister::ActionNone);
}
void KGlobalLedgerView::setupDefaultAction()
{
switch (m_account.accountType()) {
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::AssetLoan:
- case MyMoneyAccount::Savings:
+ case eMyMoney::Account::Asset:
+ case eMyMoney::Account::AssetLoan:
+ case eMyMoney::Account::Savings:
d->m_action = KMyMoneyRegister::ActionDeposit;
break;
default:
d->m_action = KMyMoneyRegister::ActionWithdrawal;
break;
}
}
bool KGlobalLedgerView::selectEmptyTransaction()
{
bool rc = false;
if (!m_inEditMode) {
// in case we don't know the type of transaction to be created,
// have at least one selected transaction and the id of
// this transaction is not empty, we take it as template for the
// transaction to be created
KMyMoneyRegister::SelectedTransactions list(m_register);
if ((d->m_action == KMyMoneyRegister::ActionNone) && (!list.isEmpty()) && (!list[0].transaction().id().isEmpty())) {
// the new transaction to be created will have the same type
// as the one that currently has the focus
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(m_register->focusItem());
if (t)
d->m_action = t->actionType();
m_register->clearSelection();
}
// if we still don't have an idea which type of transaction
// to create, we use the default.
if (d->m_action == KMyMoneyRegister::ActionNone) {
setupDefaultAction();
}
m_register->selectItem(m_register->lastItem());
m_register->updateRegister();
rc = true;
}
return rc;
}
TransactionEditor* KGlobalLedgerView::startEdit(const KMyMoneyRegister::SelectedTransactions& list)
{
// we use the warnlevel to keep track, if we have to warn the
// user that some or all splits have been reconciled or if the
// user cannot modify the transaction if at least one split
// has the status frozen. The following value are used:
//
// 0 - no sweat, user can modify
// 1 - user should be warned that at least one split has been reconciled
// already
// 2 - user will be informed, that this transaction cannot be changed anymore
int warnLevel = list.warnLevel();
Q_ASSERT(warnLevel < 2); // otherwise the edit action should not be enabled
switch (warnLevel) {
case 0:
break;
case 1:
if (KMessageBox::warningContinueCancel(0,
i18n(
"At least one split of the selected transactions has been reconciled. "
"Do you wish to continue to edit the transactions anyway?"
),
i18n("Transaction already reconciled"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
"EditReconciledTransaction") == KMessageBox::Cancel) {
warnLevel = 2;
}
break;
case 2:
KMessageBox::sorry(0,
i18n("At least one split of the selected transactions has been frozen. "
"Editing the transactions is therefore prohibited."),
i18n("Transaction already frozen"));
break;
case 3:
KMessageBox::sorry(0,
i18n("At least one split of the selected transaction references an account that has been closed. "
"Editing the transactions is therefore prohibited."),
i18n("Account closed"));
break;
}
if (warnLevel > 1)
return 0;
TransactionEditor* editor = 0;
KMyMoneyRegister::Transaction* item = dynamic_cast<KMyMoneyRegister::Transaction*>(m_register->focusItem());
if (item) {
// in case the current focus item is not selected, we move the focus to the first selected transaction
if (!item->isSelected()) {
KMyMoneyRegister::RegisterItem* p;
for (p = m_register->firstItem(); p; p = p->nextItem()) {
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
if (t && t->isSelected()) {
m_register->setFocusItem(t);
item = t;
break;
}
}
}
// decide, if we edit in the register or in the form
TransactionEditorContainer* parent;
if (m_formFrame->isVisible())
parent = m_form;
else {
parent = m_register;
}
editor = item->createEditor(parent, list, m_lastPostDate);
// check that we use the same transaction commodity in all selected transactions
// if not, we need to update this in the editor's list. The user can also bail out
// of this operation which means that we have to stop editing here.
if (editor) {
if (!editor->fixTransactionCommodity(m_account)) {
// if the user wants to quit, we need to destroy the editor
// and bail out
delete editor;
editor = 0;
}
}
if (editor) {
if (parent == m_register) {
// make sure, the height of the table is correct
m_register->updateRegister(KMyMoneyGlobalSettings::ledgerLens() | !KMyMoneyGlobalSettings::transactionForm());
}
m_inEditMode = true;
connect(editor, SIGNAL(transactionDataSufficient(bool)), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionEnter]), SLOT(setEnabled(bool)));
connect(editor, SIGNAL(returnPressed()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionEnter]), SLOT(trigger()));
connect(editor, SIGNAL(escapePressed()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionCancel]), SLOT(trigger()));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), editor, SLOT(slotReloadEditWidgets()));
connect(editor, SIGNAL(finishEdit(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotLeaveEditMode(KMyMoneyRegister::SelectedTransactions)));
connect(editor, SIGNAL(objectCreation(bool)), d->m_mousePressFilter, SLOT(setFilterDeactive(bool)));
connect(editor, SIGNAL(createPayee(QString,QString&)), kmymoney, SLOT(slotPayeeNew(QString,QString&)));
connect(editor, SIGNAL(createTag(QString,QString&)), kmymoney, SLOT(slotTagNew(QString,QString&)));
connect(editor, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount)));
connect(editor, SIGNAL(createSecurity(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotInvestmentNew(MyMoneyAccount&,MyMoneyAccount)));
connect(editor, SIGNAL(assignNumber()), kmymoney, SLOT(slotTransactionAssignNumber()));
connect(editor, SIGNAL(lastPostDateUsed(QDate)), this, SLOT(slotKeepPostDate(QDate)));
// create the widgets, place them in the parent and load them with data
// setup tab order
m_tabOrderWidgets.clear();
editor->setup(m_tabOrderWidgets, m_account, d->m_action);
Q_ASSERT(!m_tabOrderWidgets.isEmpty());
// install event filter in all taborder widgets
QWidgetList::const_iterator it_w = m_tabOrderWidgets.constBegin();
for (; it_w != m_tabOrderWidgets.constEnd(); ++it_w) {
(*it_w)->installEventFilter(this);
}
// Install a filter that checks if a mouse press happened outside
// of one of our own widgets.
qApp->installEventFilter(d->m_mousePressFilter);
// Check if the editor has some preference on where to set the focus
// If not, set the focus to the first widget in the tab order
QWidget* focusWidget = editor->firstWidget();
if (!focusWidget)
focusWidget = m_tabOrderWidgets.first();
// for some reason, this only works reliably if delayed a bit
QTimer::singleShot(10, focusWidget, SLOT(setFocus()));
// preset to 'I have no idea which type to create' for the next round.
d->m_action = KMyMoneyRegister::ActionNone;
}
}
return editor;
}
void KGlobalLedgerView::slotLeaveEditMode(const KMyMoneyRegister::SelectedTransactions& list)
{
m_inEditMode = false;
qApp->removeEventFilter(d->m_mousePressFilter);
// a possible focusOut event may have removed the focus, so we
// install it back again.
m_register->focusItem()->setFocus(true);
// if we come back from editing a new item, we make sure that
// we always select the very last known transaction entry no
// matter if the transaction has been created or not.
if (list.count() && list[0].transaction().id().isEmpty()) {
// block signals to prevent some infinite loops that might occur here.
m_register->blockSignals(true);
m_register->clearSelection();
KMyMoneyRegister::RegisterItem* p = m_register->lastItem();
if (p && p->prevItem())
p = p->prevItem();
m_register->selectItem(p);
m_register->updateRegister(true);
m_register->blockSignals(false);
// we need to update the form manually as sending signals was blocked
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
if (t)
m_form->slotSetTransaction(t);
} else {
if (!KMyMoneySettings::transactionForm()) {
// update the row height of the transactions because it might differ between viewing/editing mode when not using the transaction form
m_register->blockSignals(true);
m_register->updateRegister(true);
m_register->blockSignals(false);
}
}
if (m_needReload)
slotLoadView();
m_register->setFocus();
}
bool KGlobalLedgerView::focusNextPrevChild(bool next)
{
bool rc = false;
// qDebug("KGlobalLedgerView::focusNextPrevChild(editmode=%s)", m_inEditMode ? "true" : "false");
if (m_inEditMode) {
QWidget *w = 0;
w = qApp->focusWidget();
// qDebug("w = %p", w);
int currentWidgetIndex = m_tabOrderWidgets.indexOf(w);
while (w && currentWidgetIndex == -1) {
// qDebug("'%s' not in list, use parent", qPrintable(w->objectName()));
w = w->parentWidget();
currentWidgetIndex = m_tabOrderWidgets.indexOf(w);
}
if (currentWidgetIndex != -1) {
// if(w) qDebug("tab order is at '%s'", qPrintable(w->objectName()));
currentWidgetIndex += next ? 1 : -1;
if (currentWidgetIndex < 0)
currentWidgetIndex = m_tabOrderWidgets.size() - 1;
else if (currentWidgetIndex >= m_tabOrderWidgets.size())
currentWidgetIndex = 0;
w = m_tabOrderWidgets[currentWidgetIndex];
// qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w);
if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) {
// qDebug("Selecting '%s' (%p) as focus", qPrintable(w->objectName()), w);
w->setFocus();
rc = true;
}
}
} else
rc = KMyMoneyViewBase::focusNextPrevChild(next);
return rc;
}
void KGlobalLedgerView::showEvent(QShowEvent* event)
{
if (m_needLoad)
init();
emit aboutToShow(View::Ledgers);
if (m_needReload) {
if (!m_inEditMode) {
setUpdatesEnabled(false);
loadView();
setUpdatesEnabled(true);
m_needReload = false;
m_newAccountLoaded = false;
}
} else {
emit accountSelected(m_account);
KMyMoneyRegister::SelectedTransactions list(m_register);
emit transactionsSelected(list);
}
// don't forget base class implementation
KMyMoneyViewBase::showEvent(event);
}
bool KGlobalLedgerView::eventFilter(QObject* o, QEvent* e)
{
bool rc = false;
// Need to capture mouse position here as QEvent::ToolTip is too slow
m_tooltipPosn = QCursor::pos();
if (e->type() == QEvent::KeyPress) {
if (m_inEditMode) {
// qDebug("object = %s, key = %d", o->className(), k->key());
if (o == m_register) {
// we hide all key press events from the register
// while editing a transaction
rc = true;
}
}
}
if (!rc)
rc = KMyMoneyViewBase::eventFilter(o, e);
return rc;
}
void KGlobalLedgerView::showTooltip(const QString msg) const
{
QToolTip::showText(m_tooltipPosn, msg);
}
void KGlobalLedgerView::slotSortOptions()
{
QPointer<KSortOptionDlg> dlg = new KSortOptionDlg(this);
QString key;
QString sortOrder, def;
if (isReconciliationAccount()) {
key = "kmm-sort-reconcile";
def = KMyMoneyGlobalSettings::sortReconcileView();
} else {
key = "kmm-sort-std";
def = KMyMoneyGlobalSettings::sortNormalView();
}
// check if we have an account override of the sort order
if (!m_account.value(key).isEmpty())
sortOrder = m_account.value(key);
QString oldOrder = sortOrder;
dlg->setSortOption(sortOrder, def);
if (dlg->exec() == QDialog::Accepted) {
if (dlg != 0) {
sortOrder = dlg->sortOption();
if (sortOrder != oldOrder) {
if (sortOrder.isEmpty()) {
m_account.deletePair(key);
} else {
m_account.setValue(key, sortOrder);
}
MyMoneyFileTransaction ft;
try {
MyMoneyFile::instance()->modifyAccount(m_account);
ft.commit();
} catch (const MyMoneyException &e) {
qDebug("Unable to update sort order for account '%s': %s", qPrintable(m_account.name()), qPrintable(e.what()));
}
}
}
}
delete dlg;
}
void KGlobalLedgerView::slotToggleTransactionMark(KMyMoneyRegister::Transaction* /* t */)
{
if (!m_inEditMode) {
emit toggleReconciliationFlag();
}
}
void KGlobalLedgerView::slotKeepPostDate(const QDate& date)
{
m_lastPostDate = date;
}
bool KGlobalLedgerView::canCreateTransactions(QString& tooltip) const
{
bool rc = true;
if (m_account.id().isEmpty()) {
tooltip = i18n("Cannot create transactions when no account is selected.");
rc = false;
}
- if (m_account.accountGroup() == MyMoneyAccount::Income
- || m_account.accountGroup() == MyMoneyAccount::Expense) {
+ if (m_account.accountGroup() == eMyMoney::Account::Income
+ || m_account.accountGroup() == eMyMoney::Account::Expense) {
tooltip = i18n("Cannot create transactions in the context of a category.");
showTooltip(tooltip);
rc = false;
}
if (m_account.isClosed()) {
tooltip = i18n("Cannot create transactions in a closed account.");
showTooltip(tooltip);
rc = false;
}
return rc;
}
bool KGlobalLedgerView::canProcessTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const
{
MyMoneyAccount acc;
QString closedAccount;
if (m_register->focusItem() == 0)
return false;
bool rc = true;
if (list.warnLevel() == 3) { //Closed account somewhere
KMyMoneyRegister::SelectedTransactions::const_iterator it_t;
for (it_t = list.begin(); rc && it_t != list.end(); ++it_t) {
QList<MyMoneySplit> splitList = (*it_t).transaction().splits();
QString id = splitList.first().accountId();
acc = MyMoneyFile::instance()->account(id);
if (!acc.isClosed()) { //wrong split, try other
id = splitList.last().accountId();
acc = MyMoneyFile::instance()->account(id);
}
closedAccount = acc.name();
break;
}
tooltip = i18n("Cannot process transactions in account %1, which is closed.", closedAccount);
showTooltip(tooltip);
return false;
}
if (!m_register->focusItem()->isSelected()) {
tooltip = i18n("Cannot process transaction with focus if it is not selected.");
showTooltip(tooltip);
return false;
}
tooltip.clear();
return !list.isEmpty();
}
bool KGlobalLedgerView::canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const
{
return canProcessTransactions(list, tooltip) && list.canModify();
}
bool KGlobalLedgerView::canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const
{
return canProcessTransactions(list, tooltip) && list.canDuplicate();
}
bool KGlobalLedgerView::canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const
{
// check if we can edit the list of transactions. We can edit, if
//
// a) no mix of standard and investment transactions exist
// b) if a split transaction is selected, this is the only selection
// c) none of the splits is frozen
// d) the transaction having the current focus is selected
// check for d)
if (!canProcessTransactions(list, tooltip))
return false;
// check for c)
if (list.warnLevel() == 2) {
tooltip = i18n("Cannot edit transactions with frozen splits.");
showTooltip(tooltip);
return false;
}
bool rc = true;
int investmentTransactions = 0;
int normalTransactions = 0;
- if (m_account.accountGroup() == MyMoneyAccount::Income
- || m_account.accountGroup() == MyMoneyAccount::Expense) {
+ if (m_account.accountGroup() == eMyMoney::Account::Income
+ || m_account.accountGroup() == eMyMoney::Account::Expense) {
tooltip = i18n("Cannot edit transactions in the context of a category.");
showTooltip(tooltip);
rc = false;
}
if (m_account.isClosed()) {
tooltip = i18n("Cannot create or edit any transactions in Account %1 as it is closed", m_account.name());
showTooltip(tooltip);
rc = false;
}
KMyMoneyRegister::SelectedTransactions::const_iterator it_t;
QString action;
for (it_t = list.begin(); rc && it_t != list.end(); ++it_t) {
if ((*it_t).transaction().id().isEmpty()) {
tooltip.clear();
rc = false;
continue;
}
if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction) {
if (action.isEmpty()) {
action = (*it_t).split().action();
continue;
}
if (action == (*it_t).split().action()) {
continue;
} else {
tooltip = (i18n("Cannot edit mixed investment action/type transactions together."));
showTooltip(tooltip);
rc = false;
break;
}
}
if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction)
++investmentTransactions;
else
++normalTransactions;
// check for a)
if (investmentTransactions != 0 && normalTransactions != 0) {
tooltip = i18n("Cannot edit investment transactions and non-investment transactions together.");
showTooltip(tooltip);
rc = false;
break;
}
// check for b) but only for normalTransactions
if ((*it_t).transaction().splitCount() > 2 && normalTransactions != 0) {
if (list.count() > 1) {
tooltip = i18n("Cannot edit multiple split transactions at once.");
showTooltip(tooltip);
rc = false;
break;
}
}
}
// check for multiple transactions being selected in an investment account
// we do not allow editing in this case: https://bugs.kde.org/show_bug.cgi?id=240816
// later on, we might allow to edit investment transactions of the same type
/// Can now disable the following check.
/* if (rc == true && investmentTransactions > 1) {
tooltip = i18n("Cannot edit multiple investment transactions at once");
rc = false;
}*/
// now check that we have the correct account type for investment transactions
if (rc == true && investmentTransactions != 0) {
- if (m_account.accountType() != MyMoneyAccount::Investment) {
+ if (m_account.accountType() != eMyMoney::Account::Investment) {
tooltip = i18n("Cannot edit investment transactions in the context of this account.");
rc = false;
}
}
return rc;
}
diff --git a/kmymoney/views/khomeview.cpp b/kmymoney/views/khomeview.cpp
index 07cb270f1..b088e731a 100644
--- a/kmymoney/views/khomeview.cpp
+++ b/kmymoney/views/khomeview.cpp
@@ -1,2061 +1,2064 @@
/***************************************************************************
khomeview.cpp - description
-------------------
begin : Tue Jan 22 2002
copyright : (C) 2000-2002 by Michael Edwardes <mte@users.sourceforge.net>
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "khomeview.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
#include <QPixmap>
#include <QTimer>
#include <QBuffer>
#include <QStandardPaths>
#include <QDesktopServices>
#include <QUrlQuery>
#include <QWheelEvent>
#include <QtPrintSupport/QPrintDialog>
#include <QtPrintSupport/QPrinter>
#ifdef ENABLE_WEBENGINE
#include <QtWebEngineWidgets/QWebEngineView>
#else
#include <KDEWebKit/KWebView>
#endif
// ----------------------------------------------------------------------------
// KDE Includes
#include <KChartAbstractCoordinatePlane>
#include <KChartChart>
#include <KLocalizedString>
#include <KXmlGuiWindow>
#include <KActionCollection>
#include <KMessageBox>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyutils.h"
#include "kmymoneyutils.h"
#include "kwelcomepage.h"
#include "kmymoneyglobalsettings.h"
#include "mymoneyfile.h"
+#include "mymoneyprice.h"
#include "mymoneyforecast.h"
#include "kmymoney.h"
#include "kreportchartview.h"
#include "pivottable.h"
#include "pivotgrid.h"
#include "reportaccount.h"
#include "icons.h"
#include "kmymoneywebpage.h"
#include "mymoneyschedule.h"
+#include "mymoneyenums.h"
#define VIEW_LEDGER "ledger"
#define VIEW_SCHEDULE "schedule"
#define VIEW_WELCOME "welcome"
#define VIEW_HOME "home"
#define VIEW_REPORTS "reports"
using namespace Icons;
+using namespace eMyMoney;
bool accountNameLess(const MyMoneyAccount &acc1, const MyMoneyAccount &acc2)
{
return acc1.name().localeAwareCompare(acc2.name()) < 0;
}
using namespace reports;
class KHomeView::Private
{
public:
Private() :
m_showAllSchedules(false),
m_needReload(false),
m_needLoad(true),
m_netWorthGraphLastValidSize(400, 300) {
}
/**
* daily balances of an account
*/
typedef QMap<QDate, MyMoneyMoney> dailyBalances;
#ifdef ENABLE_WEBENGINE
QWebEngineView *m_view;
#else
KWebView *m_view;
#endif
QString m_html;
bool m_showAllSchedules;
bool m_needReload;
bool m_needLoad;
MyMoneyForecast m_forecast;
MyMoneyMoney m_total;
/**
* Hold the last valid size of the net worth graph
* for the times when the needed size can't be computed.
*/
QSize m_netWorthGraphLastValidSize;
/**
* daily forecast balance of accounts
*/
QMap<QString, dailyBalances> m_accountList;
};
/**
* @brief Converts a QPixmap to an data URI scheme
*
* According to RFC 2397
*
* @param pixmap Source to convert
* @return full data URI
*/
QString QPixmapToDataUri(const QPixmap& pixmap)
{
QImage image(pixmap.toImage());
QByteArray byteArray;
QBuffer buffer(&byteArray);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG"); // writes the image in PNG format inside the buffer
return QLatin1String("data:image/png;base64,") + QString(byteArray.toBase64());
}
KHomeView::KHomeView(QWidget *parent) :
KMyMoneyViewBase(parent),
d(new Private)
{
}
KHomeView::~KHomeView()
{
// if user wants to remember the font size, store it here
if (KMyMoneyGlobalSettings::rememberZoomFactor()) {
KMyMoneyGlobalSettings::setZoomFactor(d->m_view->zoomFactor());
KMyMoneyGlobalSettings::self()->save();
}
delete d;
}
void KHomeView::init()
{
d->m_needLoad = false;
auto vbox = new QVBoxLayout(this);
setLayout(vbox);
vbox->setSpacing(6);
vbox->setMargin(0);
#ifdef ENABLE_WEBENGINE
d->m_view = new QWebEngineView(this);
#else
d->m_view = new KWebView(this);
#endif
d->m_view->setPage(new MyQWebEnginePage(d->m_view));
vbox->addWidget(d->m_view);
d->m_view->setHtml(KWelcomePage::welcomePage(), QUrl("file://"));
#ifdef ENABLE_WEBENGINE
connect(d->m_view->page(), &QWebEnginePage::urlChanged,
this, &KHomeView::slotOpenUrl);
#else
d->m_view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
connect(d->m_view->page(), &KWebPage::linkClicked,
this, &KHomeView::slotOpenUrl);
#endif
}
void KHomeView::wheelEvent(QWheelEvent* event)
{
// Zoom text on Ctrl + Scroll
if (event->modifiers() & Qt::CTRL) {
qreal factor = d->m_view->zoomFactor();
if (event->delta() > 0)
factor += 0.1;
else if (event->delta() < 0)
factor -= 0.1;
d->m_view->setZoomFactor(factor);
event->accept();
return;
}
}
void KHomeView::slotLoadView()
{
d->m_needReload = true;
if (isVisible()) {
loadView();
d->m_needReload = false;
}
}
void KHomeView::showEvent(QShowEvent* event)
{
if (d->m_needLoad)
init();
emit aboutToShow(View::Home);
if (d->m_needReload) {
loadView();
d->m_needReload = false;
}
QWidget::showEvent(event);
}
void KHomeView::slotPrintView()
{
if (d->m_view) {
m_currentPrinter = new QPrinter();
QPrintDialog *dialog = new QPrintDialog(m_currentPrinter, this);
dialog->setWindowTitle(QString());
if (dialog->exec() != QDialog::Accepted) {
delete m_currentPrinter;
m_currentPrinter = nullptr;
return;
}
#ifdef ENABLE_WEBENGINE
d->m_view->page()->print(m_currentPrinter, [=] (bool) {delete m_currentPrinter; m_currentPrinter = nullptr;});
#else
d->m_view->print(m_currentPrinter);
#endif
}
}
void KHomeView::loadView()
{
d->m_view->setZoomFactor(KMyMoneyGlobalSettings::zoomFactor());
QList<MyMoneyAccount> list;
MyMoneyFile::instance()->accountList(list);
if (list.count() == 0) {
d->m_view->setHtml(KWelcomePage::welcomePage(), QUrl("file://"));
} else {
//clear the forecast flag so it will be reloaded
d->m_forecast.setForecastDone(false);
const QString filename = QStandardPaths::locate(QStandardPaths::AppConfigLocation, "html/kmymoney.css");
QString header = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"%1\">\n").arg(QUrl::fromLocalFile(filename).url());
header += KMyMoneyUtils::variableCSS();
header += "</head><body id=\"summaryview\">\n";
QString footer = "</body></html>\n";
d->m_html.clear();
d->m_html += header;
d->m_html += QString("<div id=\"summarytitle\">%1</div>").arg(i18n("Your Financial Summary"));
QStringList settings = KMyMoneyGlobalSettings::itemList();
QStringList::ConstIterator it;
for (it = settings.constBegin(); it != settings.constEnd(); ++it) {
int option = (*it).toInt();
if (option > 0) {
switch (option) {
case 1: // payments
showPayments();
break;
case 2: // preferred accounts
showAccounts(Preferred, i18n("Preferred Accounts"));
break;
case 3: // payment accounts
// Check if preferred accounts are shown separately
if (settings.contains("2")) {
showAccounts(static_cast<paymentTypeE>(Payment | Preferred),
i18n("Payment Accounts"));
} else {
showAccounts(Payment, i18n("Payment Accounts"));
}
break;
case 4: // favorite reports
showFavoriteReports();
break;
case 5: // forecast
showForecast();
break;
case 6: // net worth graph over all accounts
showNetWorthGraph();
break;
case 8: // assets and liabilities
showAssetsLiabilities();
break;
case 9: // budget
showBudget();
break;
case 10: // cash flow summary
showCashFlowSummary();
break;
}
d->m_html += "<div class=\"gap\">&nbsp;</div>\n";
}
}
d->m_html += "<div id=\"returnlink\">";
d->m_html += link(VIEW_WELCOME, QString()) + i18n("Show KMyMoney welcome page") + linkend();
d->m_html += "</div>";
d->m_html += "<div id=\"vieweffect\"></div>";
d->m_html += footer;
d->m_view->setHtml(d->m_html, QUrl("file://"));
}
}
void KHomeView::showNetWorthGraph()
{
d->m_html += QString("<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">%1</div>\n<div class=\"gap\">&nbsp;</div>\n").arg(i18n("Net Worth Forecast"));
MyMoneyReport reportCfg = MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::userDefined, // overridden by the setDateFilter() call below
MyMoneyReport::eDetailTotal,
i18n("Net Worth Forecast"),
i18n("Generated Report"));
reportCfg.setChartByDefault(true);
reportCfg.setChartCHGridLines(false);
reportCfg.setChartSVGridLines(false);
reportCfg.setChartDataLabels(false);
reportCfg.setChartType(MyMoneyReport::eChartLine);
reportCfg.setIncludingSchedules(false);
- reportCfg.addAccountGroup(MyMoneyAccount::Asset);
- reportCfg.addAccountGroup(MyMoneyAccount::Liability);
+ reportCfg.addAccountGroup(Account::Asset);
+ reportCfg.addAccountGroup(Account::Liability);
reportCfg.setColumnsAreDays(true);
reportCfg.setConvertCurrency(true);
reportCfg.setIncludingForecast(true);
reportCfg.setDateFilter(QDate::currentDate(), QDate::currentDate().addDays(+ 90));
reports::PivotTable table(reportCfg);
reports::KReportChartView* chartWidget = new reports::KReportChartView(0);
table.drawChart(*chartWidget);
// Adjust the size
QSize netWorthGraphSize = KHomeView::size();
netWorthGraphSize -= QSize(80, 30);
// consider the computed size valid only if it's smaller on both axes that the applications size
if (netWorthGraphSize.width() < kmymoney->width() || netWorthGraphSize.height() < kmymoney->height()) {
d->m_netWorthGraphLastValidSize = netWorthGraphSize;
}
chartWidget->resize(d->m_netWorthGraphLastValidSize);
//save the chart to an image
QString chart = QPixmapToDataUri(QPixmap::grabWidget(chartWidget->coordinatePlane()->parent()));
d->m_html += QString("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >");
d->m_html += QString("<tr>");
d->m_html += QString("<td><center><img src=\"%1\" ALT=\"Networth\" width=\"100%\" ></center></td>").arg(chart);
d->m_html += QString("</tr>");
d->m_html += QString("</table></div></div>");
//delete the widget since we no longer need it
delete chartWidget;
}
void KHomeView::showPayments()
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneySchedule> overdues;
QList<MyMoneySchedule> schedule;
int i = 0;
//if forecast has not been executed yet, do it.
if (!d->m_forecast.isForecastDone())
doForecast();
- schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_ANY,
+ schedule = file->scheduleList(QString(), Schedule::Type::Any,
+ Schedule::Occurrence::Any,
+ Schedule::PaymentType::Any,
QDate::currentDate(),
- QDate::currentDate().addMonths(1));
- overdues = file->scheduleList("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_ANY,
+ QDate::currentDate().addMonths(1), false);
+ overdues = file->scheduleList(QString(), Schedule::Type::Any,
+ Schedule::Occurrence::Any,
+ Schedule::PaymentType::Any,
QDate(), QDate(), true);
if (schedule.empty() && overdues.empty())
return;
// HACK
// Remove the finished schedules
QList<MyMoneySchedule>::Iterator d_it;
//regular schedules
d_it = schedule.begin();
while (d_it != schedule.end()) {
if ((*d_it).isFinished()) {
d_it = schedule.erase(d_it);
continue;
}
++d_it;
}
//overdue schedules
d_it = overdues.begin();
while (d_it != overdues.end()) {
if ((*d_it).isFinished()) {
d_it = overdues.erase(d_it);
continue;
}
++d_it;
}
d->m_html += "<div class=\"shadow\"><div class=\"displayblock\">";
d->m_html += QString("<div class=\"summaryheader\">%1</div>\n").arg(i18n("Payments"));
if (!overdues.isEmpty()) {
d->m_html += "<div class=\"gap\">&nbsp;</div>\n";
qSort(overdues);
QList<MyMoneySchedule>::Iterator it;
QList<MyMoneySchedule>::Iterator it_f;
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
d->m_html += QString("<tr class=\"itemtitle warningtitle\" ><td colspan=\"5\">%1</td></tr>\n").arg(showColoredAmount(i18n("Overdue payments"), true));
d->m_html += "<tr class=\"item warning\">";
d->m_html += "<td class=\"left\" width=\"10%\">";
d->m_html += i18n("Date");
d->m_html += "</td>";
d->m_html += "<td class=\"left\" width=\"40%\">";
d->m_html += i18n("Schedule");
d->m_html += "</td>";
d->m_html += "<td class=\"left\" width=\"20%\">";
d->m_html += i18n("Account");
d->m_html += "</td>";
d->m_html += "<td class=\"right\" width=\"15%\">";
d->m_html += i18n("Amount");
d->m_html += "</td>";
d->m_html += "<td class=\"right\" width=\"15%\">";
d->m_html += i18n("Balance after");
d->m_html += "</td>";
d->m_html += "</tr>";
for (it = overdues.begin(); it != overdues.end(); ++it) {
// determine number of overdue payments
int cnt =
(*it).transactionsRemainingUntil(QDate::currentDate().addDays(-1));
d->m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd");
showPaymentEntry(*it, cnt);
d->m_html += "</tr>";
}
d->m_html += "</table>";
}
if (!schedule.isEmpty()) {
qSort(schedule);
// Extract todays payments if any
QList<MyMoneySchedule> todays;
QList<MyMoneySchedule>::Iterator t_it;
for (t_it = schedule.begin(); t_it != schedule.end();) {
if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) {
todays.append(*t_it);
(*t_it).setNextDueDate((*t_it).nextPayment(QDate::currentDate()));
// if adjustedNextDueDate is still currentDate then remove it from
// scheduled payments
if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) {
t_it = schedule.erase(t_it);
continue;
}
}
++t_it;
}
if (todays.count() > 0) {
d->m_html += "<div class=\"gap\">&nbsp;</div>\n";
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
d->m_html += QString("<tr class=\"itemtitle\"><td class=\"left\" colspan=\"5\">%1</td></tr>\n").arg(i18n("Today's due payments"));
d->m_html += "<tr class=\"item\">";
d->m_html += "<td class=\"left\" width=\"10%\">";
d->m_html += i18n("Date");
d->m_html += "</td>";
d->m_html += "<td class=\"left\" width=\"40%\">";
d->m_html += i18n("Schedule");
d->m_html += "</td>";
d->m_html += "<td class=\"left\" width=\"20%\">";
d->m_html += i18n("Account");
d->m_html += "</td>";
d->m_html += "<td class=\"right\" width=\"15%\">";
d->m_html += i18n("Amount");
d->m_html += "</td>";
d->m_html += "<td class=\"right\" width=\"15%\">";
d->m_html += i18n("Balance after");
d->m_html += "</td>";
d->m_html += "</tr>";
for (t_it = todays.begin(); t_it != todays.end(); ++t_it) {
d->m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd");
showPaymentEntry(*t_it);
d->m_html += "</tr>";
}
d->m_html += "</table>";
}
if (!schedule.isEmpty()) {
d->m_html += "<div class=\"gap\">&nbsp;</div>\n";
QList<MyMoneySchedule>::Iterator it;
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
d->m_html += QString("<tr class=\"itemtitle\"><td class=\"left\" colspan=\"5\">%1</td></tr>\n").arg(i18n("Future payments"));
d->m_html += "<tr class=\"item\">";
d->m_html += "<td class=\"left\" width=\"10%\">";
d->m_html += i18n("Date");
d->m_html += "</td>";
d->m_html += "<td class=\"left\" width=\"40%\">";
d->m_html += i18n("Schedule");
d->m_html += "</td>";
d->m_html += "<td class=\"left\" width=\"20%\">";
d->m_html += i18n("Account");
d->m_html += "</td>";
d->m_html += "<td class=\"right\" width=\"15%\">";
d->m_html += i18n("Amount");
d->m_html += "</td>";
d->m_html += "<td class=\"right\" width=\"15%\">";
d->m_html += i18n("Balance after");
d->m_html += "</td>";
d->m_html += "</tr>";
// show all or the first 6 entries
int cnt;
cnt = (d->m_showAllSchedules) ? -1 : 6;
bool needMoreLess = d->m_showAllSchedules;
QDate lastDate = QDate::currentDate().addMonths(1);
qSort(schedule);
do {
it = schedule.begin();
if (it == schedule.end())
break;
// if the next due date is invalid (schedule is finished)
// we remove it from the list
QDate nextDate = (*it).nextDueDate();
if (!nextDate.isValid()) {
schedule.erase(it);
continue;
}
if (nextDate > lastDate)
break;
if (cnt == 0) {
needMoreLess = true;
break;
}
// in case we've shown the current recurrence as overdue,
// we don't show it here again, but keep the schedule
// as it might show up later in the list again
if (!(*it).isOverdue()) {
if (cnt > 0)
--cnt;
d->m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd");
showPaymentEntry(*it);
d->m_html += "</tr>";
// for single occurrence we have reported everything so we
// better get out of here.
- if ((*it).occurrence() == MyMoneySchedule::OCCUR_ONCE) {
+ if ((*it).occurrence() == Schedule::Occurrence::Once) {
schedule.erase(it);
continue;
}
}
// if nextPayment returns an invalid date, setNextDueDate will
// just skip it, resulting in a loop
// we check the resulting date and erase the schedule if invalid
if (!((*it).nextPayment((*it).nextDueDate())).isValid()) {
schedule.erase(it);
continue;
}
(*it).setNextDueDate((*it).nextPayment((*it).nextDueDate()));
qSort(schedule);
} while (1);
if (needMoreLess) {
d->m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd");
d->m_html += "<td colspan=\"5\">";
if (d->m_showAllSchedules) {
d->m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("reduced")) + i18nc("Less...", "Show fewer schedules on the list") + linkend();
} else {
d->m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("full")) + i18nc("More...", "Show more schedules on the list") + linkend();
}
d->m_html += "</td>";
d->m_html += "</tr>";
}
d->m_html += "</table>";
}
}
d->m_html += "</div></div>";
}
void KHomeView::showPaymentEntry(const MyMoneySchedule& sched, int cnt)
{
QString tmp;
MyMoneyFile* file = MyMoneyFile::instance();
try {
MyMoneyAccount acc = sched.account();
if (!acc.id().isEmpty()) {
MyMoneyTransaction t = sched.transaction();
// only show the entry, if it is still active
if (!sched.isFinished()) {
MyMoneySplit sp = t.splitByAccount(acc.id(), true);
QString pathEnter = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::KeyEnter]).pixmap(QSize(16,16)));
QString pathSkip = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::MediaSkipForward]).pixmap(QSize(16,16)));
//show payment date
tmp = QString("<td>") +
QLocale().toString(sched.adjustedNextDueDate(), QLocale::ShortFormat) +
"</td><td>";
if (!pathEnter.isEmpty())
tmp += link(VIEW_SCHEDULE, QString("?id=%1&amp;mode=enter").arg(sched.id()), i18n("Enter schedule")) + QString("<img src=\"%1\" border=\"0\"></a>").arg(pathEnter) + linkend();
if (!pathSkip.isEmpty())
tmp += "&nbsp;" + link(VIEW_SCHEDULE, QString("?id=%1&amp;mode=skip").arg(sched.id()), i18n("Skip schedule")) + QString("<img src=\"%1\" border=\"0\"></a>").arg(pathSkip) + linkend();
tmp += QString("&nbsp;");
tmp += link(VIEW_SCHEDULE, QString("?id=%1&amp;mode=edit").arg(sched.id()), i18n("Edit schedule")) + sched.name() + linkend();
//show quantity of payments overdue if any
if (cnt > 1)
tmp += i18np(" (%1 payment)", " (%1 payments)", cnt);
//show account of the main split
tmp += "</td><td>";
tmp += QString(file->account(acc.id()).name());
//show amount of the schedule
tmp += "</td><td align=\"right\">";
const MyMoneySecurity& currency = MyMoneyFile::instance()->currency(acc.currencyId());
MyMoneyMoney payment = MyMoneyMoney(sp.value(t.commodity(), acc.currencyId()) * cnt);
QString amount = MyMoneyUtils::formatMoney(payment, acc, currency);
amount.replace(QChar(' '), "&nbsp;");
tmp += showColoredAmount(amount, payment.isNegative());
tmp += "</td>";
//show balance after payments
tmp += "<td align=\"right\">";
QDate paymentDate = QDate(sched.adjustedNextDueDate());
MyMoneyMoney balanceAfter = forecastPaymentBalance(acc, payment, paymentDate);
QString balance = MyMoneyUtils::formatMoney(balanceAfter, acc, currency);
balance.replace(QChar(' '), "&nbsp;");
tmp += showColoredAmount(balance, balanceAfter.isNegative());
tmp += "</td>";
// qDebug("paymentEntry = '%s'", tmp.toLatin1());
d->m_html += tmp;
}
}
} catch (const MyMoneyException &e) {
qDebug("Unable to display schedule entry: %s", qPrintable(e.what()));
}
}
void KHomeView::showAccounts(KHomeView::paymentTypeE type, const QString& header)
{
MyMoneyFile* file = MyMoneyFile::instance();
int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction());
QList<MyMoneyAccount> accounts;
bool showClosedAccounts = kmymoney->isActionToggled(Action::ViewShowAll);
// get list of all accounts
file->accountList(accounts);
for (QList<MyMoneyAccount>::Iterator it = accounts.begin(); it != accounts.end();) {
bool removeAccount = false;
if (!(*it).isClosed() || showClosedAccounts) {
switch ((*it).accountType()) {
- case MyMoneyAccount::Expense:
- case MyMoneyAccount::Income:
+ case Account::Expense:
+ case Account::Income:
// never show a category account
// Note: This might be different in a future version when
// the homepage also shows category based information
removeAccount = true;
break;
// Asset and Liability accounts are only shown if they
// have the preferred flag set
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Investment:
+ case Account::Asset:
+ case Account::Liability:
+ case Account::Investment:
// if preferred accounts are requested, then keep in list
if ((*it).value("PreferredAccount") != "Yes"
|| (type & Preferred) == 0) {
removeAccount = true;
}
break;
// Check payment accounts. If payment and preferred is selected,
// then always show them. If only payment is selected, then
// show only if preferred flag is not set.
- case MyMoneyAccount::Checkings:
- case MyMoneyAccount::Savings:
- case MyMoneyAccount::Cash:
- case MyMoneyAccount::CreditCard:
+ case Account::Checkings:
+ case Account::Savings:
+ case Account::Cash:
+ case Account::CreditCard:
switch (type & (Payment | Preferred)) {
case Payment:
if ((*it).value("PreferredAccount") == "Yes")
removeAccount = true;
break;
case Preferred:
if ((*it).value("PreferredAccount") != "Yes")
removeAccount = true;
break;
case Payment | Preferred:
break;
default:
removeAccount = true;
break;
}
break;
// filter all accounts that are not used on homepage views
default:
removeAccount = true;
break;
}
} else if ((*it).isClosed() || (*it).isInvest()) {
// don't show if closed or a stock account
removeAccount = true;
}
if (removeAccount)
it = accounts.erase(it);
else
++it;
}
if (!accounts.isEmpty()) {
// sort the accounts by name
qStableSort(accounts.begin(), accounts.end(), accountNameLess);
QString tmp;
int i = 0;
tmp = "<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">" + header + "</div>\n<div class=\"gap\">&nbsp;</div>\n";
d->m_html += tmp;
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
d->m_html += "<tr class=\"item\">";
if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) {
QString pathStatusHeader = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::Download]).pixmap(QSize(16,16)));
d->m_html += QString("<td class=\"center\"><img src=\"%1\" border=\"0\"></td>").arg(pathStatusHeader);
}
d->m_html += "<td class=\"left\" width=\"35%\">";
d->m_html += i18n("Account");
d->m_html += "</td>";
if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions())
d->m_html += QString("<td class=\"center\">!M</td>");
if (KMyMoneyGlobalSettings::showCountOfClearedTransactions())
d->m_html += QString("<td class=\"center\">C</td>");
if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions())
d->m_html += QString("<td class=\"center\">!R</td>");
d->m_html += "<td width=\"25%\" class=\"right\">";
d->m_html += i18n("Current Balance");
d->m_html += "</td>";
//only show limit info if user chose to do so
if (KMyMoneyGlobalSettings::showLimitInfo()) {
d->m_html += "<td width=\"40%\" class=\"right\">";
d->m_html += i18n("To Minimum Balance / Maximum Credit");
d->m_html += "</td>";
}
d->m_html += "</tr>";
d->m_total = 0;
QList<MyMoneyAccount>::const_iterator it_m;
for (it_m = accounts.constBegin(); it_m != accounts.constEnd(); ++it_m) {
d->m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd");
showAccountEntry(*it_m);
d->m_html += "</tr>";
}
d->m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd");
QString amount = d->m_total.formatMoney(file->baseCurrency().tradingSymbol(), prec);
if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) d->m_html += "<td></td>";
d->m_html += QString("<td class=\"right\"><b>%1</b></td>").arg(i18n("Total"));
if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) d->m_html += "<td></td>";
if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) d->m_html += "<td></td>";
if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) d->m_html += "<td></td>";
d->m_html += QString("<td class=\"right\"><b>%1</b></td></tr>").arg(showColoredAmount(amount, d->m_total.isNegative()));
d->m_html += "</table></div></div>";
}
}
void KHomeView::showAccountEntry(const MyMoneyAccount& acc)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneySecurity currency = file->currency(acc.currencyId());
MyMoneyMoney value;
bool showLimit = KMyMoneyGlobalSettings::showLimitInfo();
- if (acc.accountType() == MyMoneyAccount::Investment) {
+ if (acc.accountType() == Account::Investment) {
//investment accounts show the balances of all its subaccounts
value = investmentBalance(acc);
//investment accounts have no minimum balance
showAccountEntry(acc, value, MyMoneyMoney(), showLimit);
} else {
//get balance for normal accounts
value = file->balance(acc.id(), QDate::currentDate());
if (acc.currencyId() != file->baseCurrency().id()) {
ReportAccount repAcc = ReportAccount(acc.id());
MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate());
MyMoneyMoney baseValue = value * curPrice;
baseValue = baseValue.convert(file->baseCurrency().smallestAccountFraction());
d->m_total += baseValue;
} else {
d->m_total += value;
}
//if credit card or checkings account, show maximum credit
- if (acc.accountType() == MyMoneyAccount::CreditCard ||
- acc.accountType() == MyMoneyAccount::Checkings) {
+ if (acc.accountType() == Account::CreditCard ||
+ acc.accountType() == Account::Checkings) {
QString maximumCredit = acc.value("maxCreditAbsolute");
if (maximumCredit.isEmpty()) {
maximumCredit = acc.value("minBalanceAbsolute");
}
MyMoneyMoney maxCredit = MyMoneyMoney(maximumCredit);
showAccountEntry(acc, value, value - maxCredit, showLimit);
} else {
//otherwise use minimum balance
QString minimumBalance = acc.value("minBalanceAbsolute");
MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance);
showAccountEntry(acc, value, value - minBalance, showLimit);
}
}
}
void KHomeView::showAccountEntry(const MyMoneyAccount& acc, const MyMoneyMoney& value, const MyMoneyMoney& valueToMinBal, const bool showMinBal)
{
MyMoneyFile* file = MyMoneyFile::instance();
QString tmp;
MyMoneySecurity currency = file->currency(acc.currencyId());
QString amount;
QString amountToMinBal;
//format amounts
amount = MyMoneyUtils::formatMoney(value, acc, currency);
amount.replace(QChar(' '), "&nbsp;");
if (showMinBal) {
amountToMinBal = MyMoneyUtils::formatMoney(valueToMinBal, acc, currency);
amountToMinBal.replace(QChar(' '), "&nbsp;");
}
QString cellStatus, cellCounts, pathOK, pathTODO, pathNotOK;
if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) {
//show account's online-status
pathOK = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::DialogOKApply]).pixmap(QSize(16,16)));
pathTODO = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::MailReceive]).pixmap(QSize(16,16)));
pathNotOK = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::DialogCancel]).pixmap(QSize(16,16)));
if (acc.value("lastImportedTransactionDate").isEmpty() || acc.value("lastStatementBalance").isEmpty())
cellStatus = '-';
else if (file->hasMatchingOnlineBalance(acc)) {
if (file->hasNewerTransaction(acc.id(), QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate)))
cellStatus = QString("<img src=\"%1\" border=\"0\">").arg(pathTODO);
else
cellStatus = QString("<img src=\"%1\" border=\"0\">").arg(pathOK);
} else
cellStatus = QString("<img src=\"%1\" border=\"0\">").arg(pathNotOK);
tmp = QString("<td class=\"center\">%1</td>").arg(cellStatus);
}
tmp += QString("<td>") +
link(VIEW_LEDGER, QString("?id=%1").arg(acc.id())) + acc.name() + linkend() + "</td>";
int countNotMarked = 0, countCleared = 0, countNotReconciled = 0;
QString countStr;
if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions() || KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions())
- countNotMarked = file->countTransactionsWithSpecificReconciliationState(acc.id(), MyMoneyTransactionFilter::notReconciled);
+ countNotMarked = file->countTransactionsWithSpecificReconciliationState(acc.id(), TransactionFilter::State::NotReconciled);
if (KMyMoneyGlobalSettings::showCountOfClearedTransactions() || KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions())
- countCleared = file->countTransactionsWithSpecificReconciliationState(acc.id(), MyMoneyTransactionFilter::cleared);
+ countCleared = file->countTransactionsWithSpecificReconciliationState(acc.id(), TransactionFilter::State::Cleared);
if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions())
countNotReconciled = countNotMarked + countCleared;
if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) {
if (countNotMarked)
countStr = QString("%1").arg(countNotMarked);
else
countStr = '-';
tmp += QString("<td class=\"center\">%1</td>").arg(countStr);
}
if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) {
if (countCleared)
countStr = QString("%1").arg(countCleared);
else
countStr = '-';
tmp += QString("<td class=\"center\">%1</td>").arg(countStr);
}
if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) {
if (countNotReconciled)
countStr = QString("%1").arg(countNotReconciled);
else
countStr = '-';
tmp += QString("<td class=\"center\">%1</td>").arg(countStr);
}
//show account balance
tmp += QString("<td class=\"right\">%1</td>").arg(showColoredAmount(amount, value.isNegative()));
//show minimum balance column if requested
if (showMinBal) {
//if it is an investment, show minimum balance empty
- if (acc.accountType() == MyMoneyAccount::Investment) {
+ if (acc.accountType() == Account::Investment) {
tmp += QString("<td class=\"right\">&nbsp;</td>");
} else {
//show minimum balance entry
tmp += QString("<td class=\"right\">%1</td>").arg(showColoredAmount(amountToMinBal, valueToMinBal.isNegative()));
}
}
// qDebug("accountEntry = '%s'", tmp.toLatin1());
d->m_html += tmp;
}
MyMoneyMoney KHomeView::investmentBalance(const MyMoneyAccount& acc)
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyMoney value;
value = file->balance(acc.id(), QDate::currentDate());
QList<QString>::const_iterator it_a;
for (it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) {
MyMoneyAccount stock = file->account(*it_a);
if (!stock.isClosed()) {
try {
MyMoneyMoney val;
MyMoneyMoney balance = file->balance(stock.id(), QDate::currentDate());
MyMoneySecurity security = file->security(stock.currencyId());
const MyMoneyPrice &price = file->price(stock.currencyId(), security.tradingCurrency());
val = (balance * price.rate(security.tradingCurrency())).convertPrecision(security.pricePrecision());
// adjust value of security to the currency of the account
MyMoneySecurity accountCurrency = file->currency(acc.currencyId());
val = val * file->price(security.tradingCurrency(), accountCurrency.id()).rate(accountCurrency.id());
val = val.convert(acc.fraction());
value += val;
} catch (const MyMoneyException &e) {
qWarning("%s", qPrintable(QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e.what())));
}
}
}
return value;
}
void KHomeView::showFavoriteReports()
{
QList<MyMoneyReport> reports = MyMoneyFile::instance()->reportList();
if (!reports.isEmpty()) {
bool firstTime = 1;
int row = 0;
QList<MyMoneyReport>::const_iterator it_report = reports.constBegin();
while (it_report != reports.constEnd()) {
if ((*it_report).isFavorite()) {
if (firstTime) {
d->m_html += QString("<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">%1</div>\n<div class=\"gap\">&nbsp;</div>\n").arg(i18n("Favorite Reports"));
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
d->m_html += "<tr class=\"item\"><td class=\"left\" width=\"40%\">";
d->m_html += i18n("Report");
d->m_html += "</td><td width=\"60%\" class=\"left\">";
d->m_html += i18n("Comment");
d->m_html += "</td></tr>";
firstTime = false;
}
d->m_html += QString("<tr class=\"row-%1\"><td>%2%3%4</td><td align=\"left\">%5</td></tr>")
.arg(row++ & 0x01 ? "even" : "odd")
.arg(link(VIEW_REPORTS, QString("?id=%1").arg((*it_report).id())))
.arg((*it_report).name())
.arg(linkend())
.arg((*it_report).comment());
}
++it_report;
}
if (!firstTime)
d->m_html += "</table></div></div>";
}
}
void KHomeView::showForecast()
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyAccount> accList;
//if forecast has not been executed yet, do it.
if (!d->m_forecast.isForecastDone())
doForecast();
accList = d->m_forecast.accountList();
if (accList.count() > 0) {
// sort the accounts by name
qStableSort(accList.begin(), accList.end(), accountNameLess);
int i = 0;
int colspan = 1;
//get begin day
int beginDay = QDate::currentDate().daysTo(d->m_forecast.beginForecastDate());
//if begin day is today skip to next cycle
if (beginDay == 0)
beginDay = d->m_forecast.accountsCycle();
// Now output header
d->m_html += QString("<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">%1</div>\n<div class=\"gap\">&nbsp;</div>\n").arg(i18n("%1 Day Forecast", d->m_forecast.forecastDays()));
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
d->m_html += "<tr class=\"item\"><td class=\"left\" width=\"40%\">";
d->m_html += i18n("Account");
d->m_html += "</td>";
int colWidth = 55 / (d->m_forecast.forecastDays() / d->m_forecast.accountsCycle());
for (i = 0; (i*d->m_forecast.accountsCycle() + beginDay) <= d->m_forecast.forecastDays(); ++i) {
d->m_html += QString("<td width=\"%1%\" class=\"right\">").arg(colWidth);
d->m_html += i18ncp("Forecast days", "%1 day", "%1 days", i * d->m_forecast.accountsCycle() + beginDay);
d->m_html += "</td>";
colspan++;
}
d->m_html += "</tr>";
// Now output entries
i = 0;
QList<MyMoneyAccount>::ConstIterator it_account;
for (it_account = accList.constBegin(); it_account != accList.constEnd(); ++it_account) {
//MyMoneyAccount acc = (*it_n);
d->m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd");
d->m_html += QString("<td width=\"40%\">") +
link(VIEW_LEDGER, QString("?id=%1").arg((*it_account).id())) + (*it_account).name() + linkend() + "</td>";
int dropZero = -1; //account dropped below zero
int dropMinimum = -1; //account dropped below minimum balance
QString minimumBalance = (*it_account).value("minimumBalance");
MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance);
MyMoneySecurity currency;
MyMoneyMoney forecastBalance;
//change account to deep currency if account is an investment
if ((*it_account).isInvest()) {
MyMoneySecurity underSecurity = file->security((*it_account).currencyId());
currency = file->security(underSecurity.tradingCurrency());
} else {
currency = file->security((*it_account).currencyId());
}
for (int f = beginDay; f <= d->m_forecast.forecastDays(); f += d->m_forecast.accountsCycle()) {
forecastBalance = d->m_forecast.forecastBalance(*it_account, QDate::currentDate().addDays(f));
QString amount;
amount = MyMoneyUtils::formatMoney(forecastBalance, *it_account, currency);
amount.replace(QChar(' '), "&nbsp;");
d->m_html += QString("<td width=\"%1%\" align=\"right\">").arg(colWidth);
d->m_html += QString("%1</td>").arg(showColoredAmount(amount, forecastBalance.isNegative()));
}
d->m_html += "</tr>";
//Check if the account is going to be below zero or below the minimal balance in the forecast period
//Check if the account is going to be below minimal balance
dropMinimum = d->m_forecast.daysToMinimumBalance(*it_account);
//Check if the account is going to be below zero in the future
dropZero = d->m_forecast.daysToZeroBalance(*it_account);
// spit out possible warnings
QString msg;
// if a minimum balance has been specified, an appropriate warning will
// only be shown, if the drop below 0 is on a different day or not present
if (dropMinimum != -1
&& !minBalance.isZero()
&& (dropMinimum < dropZero
|| dropZero == -1)) {
switch (dropMinimum) {
case -1:
break;
case 0:
msg = i18n("The balance of %1 is below the minimum balance %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency));
msg = showColoredAmount(msg, true);
break;
default:
msg = i18np("The balance of %2 will drop below the minimum balance %3 in %1 day.",
"The balance of %2 will drop below the minimum balance %3 in %1 days.",
dropMinimum - 1, (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency));
msg = showColoredAmount(msg, true);
break;
}
if (!msg.isEmpty()) {
d->m_html += QString("<tr class=\"warning\" style=\"font-weight: normal;\" ><td colspan=%2 align=\"center\" >%1</td></tr>").arg(msg).arg(colspan);
}
}
// a drop below zero is always shown
msg.clear();
switch (dropZero) {
case -1:
break;
case 0:
- if ((*it_account).accountGroup() == MyMoneyAccount::Asset) {
+ if ((*it_account).accountGroup() == Account::Asset) {
msg = i18n("The balance of %1 is below %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency));
msg = showColoredAmount(msg, true);
break;
}
- if ((*it_account).accountGroup() == MyMoneyAccount::Liability) {
+ if ((*it_account).accountGroup() == Account::Liability) {
msg = i18n("The balance of %1 is above %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency));
break;
}
break;
default:
- if ((*it_account).accountGroup() == MyMoneyAccount::Asset) {
+ if ((*it_account).accountGroup() == Account::Asset) {
msg = i18np("The balance of %2 will drop below %3 in %1 day.",
"The balance of %2 will drop below %3 in %1 days.",
dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency));
msg = showColoredAmount(msg, true);
break;
}
- if ((*it_account).accountGroup() == MyMoneyAccount::Liability) {
+ if ((*it_account).accountGroup() == Account::Liability) {
msg = i18np("The balance of %2 will raise above %3 in %1 day.",
"The balance of %2 will raise above %3 in %1 days.",
dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency));
break;
}
}
if (!msg.isEmpty()) {
d->m_html += QString("<tr class=\"warning\"><td colspan=%2 align=\"center\" ><b>%1</b></td></tr>").arg(msg).arg(colspan);
}
}
d->m_html += "</table></div></div>";
}
}
QString KHomeView::link(const QString& view, const QString& query, const QString& _title) const
{
QString titlePart;
QString title(_title);
if (!title.isEmpty())
titlePart = QString(" title=\"%1\"").arg(title.replace(QLatin1Char(' '), "&nbsp;"));
return QString("<a href=\"/%1%2\"%3>").arg(view, query, titlePart);
}
QString KHomeView::linkend() const
{
return QStringLiteral("</a>");
}
void KHomeView::slotOpenUrl(const QUrl &url)
{
QString protocol = url.scheme();
QString view = url.fileName();
if (view.isEmpty())
return;
QUrlQuery query(url);
QString id = query.queryItemValue("id");
QString mode = query.queryItemValue("mode");
if (protocol == QLatin1String("http")) {
QDesktopServices::openUrl(url);
} else if (protocol == QLatin1String("mailto")) {
QDesktopServices::openUrl(url);
} else {
KXmlGuiWindow* mw = KMyMoneyUtils::mainWindow();
Q_CHECK_PTR(mw);
if (view == VIEW_LEDGER) {
emit ledgerSelected(id, QString());
} else if (view == VIEW_SCHEDULE) {
if (mode == QLatin1String("enter")) {
emit scheduleSelected(id);
QTimer::singleShot(0, mw->actionCollection()->action(kmymoney->s_Actions[Action::ScheduleEnter]), SLOT(trigger()));
} else if (mode == QLatin1String("edit")) {
emit scheduleSelected(id);
QTimer::singleShot(0, mw->actionCollection()->action(kmymoney->s_Actions[Action::ScheduleEdit]), SLOT(trigger()));
} else if (mode == QLatin1String("skip")) {
emit scheduleSelected(id);
QTimer::singleShot(0, mw->actionCollection()->action(kmymoney->s_Actions[Action::ScheduleSkip]), SLOT(trigger()));
} else if (mode == QLatin1String("full")) {
d->m_showAllSchedules = true;
loadView();
} else if (mode == QLatin1String("reduced")) {
d->m_showAllSchedules = false;
loadView();
}
} else if (view == VIEW_REPORTS) {
emit reportSelected(id);
} else if (view == VIEW_WELCOME) {
if (mode == QLatin1String("whatsnew"))
d->m_view->setHtml(KWelcomePage::whatsNewPage(), QUrl("file://"));
else
d->m_view->setHtml(KWelcomePage::welcomePage(), QUrl("file://"));
} else if (view == QLatin1String("action")) {
QTimer::singleShot(0, mw->actionCollection()->action(id), SLOT(trigger()));
} else if (view == VIEW_HOME) {
QList<MyMoneyAccount> list;
MyMoneyFile::instance()->accountList(list);
if (list.count() == 0) {
KMessageBox::information(this, i18n("Before KMyMoney can give you detailed information about your financial status, you need to create at least one account. Until then, KMyMoney shows the welcome page instead."));
}
loadView();
} else {
qDebug("Unknown view '%s' in KHomeView::slotOpenURL()", qPrintable(view));
}
}
}
void KHomeView::showAssetsLiabilities()
{
QList<MyMoneyAccount> accounts;
QList<MyMoneyAccount>::ConstIterator it;
QList<MyMoneyAccount> assets;
QList<MyMoneyAccount> liabilities;
MyMoneyMoney netAssets;
MyMoneyMoney netLiabilities;
QString fontStart, fontEnd;
MyMoneyFile* file = MyMoneyFile::instance();
int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction());
int i = 0;
// get list of all accounts
file->accountList(accounts);
for (it = accounts.constBegin(); it != accounts.constEnd();) {
if (!(*it).isClosed()) {
switch ((*it).accountType()) {
// group all assets into one list but make sure that investment accounts always show up
- case MyMoneyAccount::Investment:
+ case Account::Investment:
assets << *it;
break;
- case MyMoneyAccount::Checkings:
- case MyMoneyAccount::Savings:
- case MyMoneyAccount::Cash:
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::AssetLoan:
+ case Account::Checkings:
+ case Account::Savings:
+ case Account::Cash:
+ case Account::Asset:
+ case Account::AssetLoan:
// list account if it's the last in the hierarchy or has transactions in it
if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) {
assets << *it;
}
break;
// group the liabilities into the other
- case MyMoneyAccount::CreditCard:
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Loan:
+ case Account::CreditCard:
+ case Account::Liability:
+ case Account::Loan:
// list account if it's the last in the hierarchy or has transactions in it
if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) {
liabilities << *it;
}
break;
default:
break;
}
}
++it;
}
//only do it if we have assets or liabilities account
if (assets.count() > 0 || liabilities.count() > 0) {
// sort the accounts by name
qStableSort(assets.begin(), assets.end(), accountNameLess);
qStableSort(liabilities.begin(), liabilities.end(), accountNameLess);
QString statusHeader;
if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) {
QString pathStatusHeader;
pathStatusHeader = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::ViewOutbox]).pixmap(QSize(16,16)));
statusHeader = QString("<img src=\"%1\" border=\"0\">").arg(pathStatusHeader);
}
//print header
d->m_html += "<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">" + i18n("Assets and Liabilities Summary") + "</div>\n<div class=\"gap\">&nbsp;</div>\n";
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
//column titles
d->m_html += "<tr class=\"item\">";
if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) {
d->m_html += "<td class=\"setcolor\">";
d->m_html += statusHeader;
d->m_html += "</td>";
}
d->m_html += "<td class=\"left\" width=\"30%\">";
d->m_html += i18n("Asset Accounts");
d->m_html += "</td>";
if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions())
d->m_html += "<td class=\"setcolor\">!M</td>";
if (KMyMoneyGlobalSettings::showCountOfClearedTransactions())
d->m_html += "<td class=\"setcolor\">C</td>";
if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions())
d->m_html += "<td class=\"setcolor\">!R</td>";
d->m_html += "<td width=\"15%\" class=\"right\">";
d->m_html += i18n("Current Balance");
d->m_html += "</td>";
//intermediate row to separate both columns
d->m_html += "<td width=\"10%\" class=\"setcolor\"></td>";
if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) {
d->m_html += "<td class=\"setcolor\">";
d->m_html += statusHeader;
d->m_html += "</td>";
}
d->m_html += "<td class=\"left\" width=\"30%\">";
d->m_html += i18n("Liability Accounts");
d->m_html += "</td>";
if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions())
d->m_html += "<td class=\"setcolor\">!M</td>";
if (KMyMoneyGlobalSettings::showCountOfClearedTransactions())
d->m_html += "<td class=\"setcolor\">C</td>";
if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions())
d->m_html += "<td class=\"setcolor\">!R</td>";
d->m_html += "<td width=\"15%\" class=\"right\">";
d->m_html += i18n("Current Balance");
d->m_html += "</td></tr>";
QString placeHolder_Status, placeHolder_Counts;
if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) placeHolder_Status = "<td></td>";
if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) placeHolder_Counts = "<td></td>";
if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) placeHolder_Counts += "<td></td>";
if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) placeHolder_Counts += "<td></td>";
//get asset and liability accounts
QList<MyMoneyAccount>::const_iterator asset_it = assets.constBegin();
QList<MyMoneyAccount>::const_iterator liabilities_it = liabilities.constBegin();
for (; asset_it != assets.constEnd() || liabilities_it != liabilities.constEnd();) {
d->m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd");
//write an asset account if we still have any
if (asset_it != assets.constEnd()) {
MyMoneyMoney value;
//investment accounts consolidate the balance of its subaccounts
- if ((*asset_it).accountType() == MyMoneyAccount::Investment) {
+ if ((*asset_it).accountType() == Account::Investment) {
value = investmentBalance(*asset_it);
} else {
value = MyMoneyFile::instance()->balance((*asset_it).id(), QDate::currentDate());
}
//calculate balance for foreign currency accounts
if ((*asset_it).currencyId() != file->baseCurrency().id()) {
ReportAccount repAcc = ReportAccount((*asset_it).id());
MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate());
MyMoneyMoney baseValue = value * curPrice;
baseValue = baseValue.convert(10000);
netAssets += baseValue;
} else {
netAssets += value;
}
//show the account without minimum balance
showAccountEntry(*asset_it, value, MyMoneyMoney(), false);
++asset_it;
} else {
//write a white space if we don't
d->m_html += QString("%1<td></td>%2<td></td>").arg(placeHolder_Status).arg(placeHolder_Counts);
}
//leave the intermediate column empty
d->m_html += "<td class=\"setcolor\"></td>";
//write a liability account
if (liabilities_it != liabilities.constEnd()) {
MyMoneyMoney value;
value = MyMoneyFile::instance()->balance((*liabilities_it).id(), QDate::currentDate());
//calculate balance if foreign currency
if ((*liabilities_it).currencyId() != file->baseCurrency().id()) {
ReportAccount repAcc = ReportAccount((*liabilities_it).id());
MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate());
MyMoneyMoney baseValue = value * curPrice;
baseValue = baseValue.convert(10000);
netLiabilities += baseValue;
} else {
netLiabilities += value;
}
//show the account without minimum balance
showAccountEntry(*liabilities_it, value, MyMoneyMoney(), false);
++liabilities_it;
} else {
//leave the space empty if we run out of liabilities
d->m_html += QString("%1<td></td>%2<td></td>").arg(placeHolder_Status).arg(placeHolder_Counts);
}
d->m_html += "</tr>";
}
//calculate net worth
MyMoneyMoney netWorth = netAssets + netLiabilities;
//format assets, liabilities and net worth
QString amountAssets = netAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString amountLiabilities = netLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString amountNetWorth = netWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec);
amountAssets.replace(QChar(' '), "&nbsp;");
amountLiabilities.replace(QChar(' '), "&nbsp;");
amountNetWorth.replace(QChar(' '), "&nbsp;");
d->m_html += QString("<tr class=\"row-%1\" style=\"font-weight:bold;\">").arg(i++ & 0x01 ? "even" : "odd");
//print total for assets
d->m_html += QString("%1<td class=\"left\">%2</td>%3<td align=\"right\">%4</td>").arg(placeHolder_Status).arg(i18n("Total Assets")).arg(placeHolder_Counts).arg(showColoredAmount(amountAssets, netAssets.isNegative()));
//leave the intermediate column empty
d->m_html += "<td class=\"setcolor\"></td>";
//print total liabilities
d->m_html += QString("%1<td class=\"left\">%2</td>%3<td align=\"right\">%4</td>").arg(placeHolder_Status).arg(i18n("Total Liabilities")).arg(placeHolder_Counts).arg(showColoredAmount(amountLiabilities, netLiabilities.isNegative()));
d->m_html += "</tr>";
//print net worth
d->m_html += QString("<tr class=\"row-%1\" style=\"font-weight:bold;\">").arg(i++ & 0x01 ? "even" : "odd");
d->m_html += QString("%1<td></td><td></td>%2<td class=\"setcolor\"></td>").arg(placeHolder_Status).arg(placeHolder_Counts);
d->m_html += QString("%1<td class=\"left\">%2</td>%3<td align=\"right\">%4</td>").arg(placeHolder_Status).arg(i18n("Net Worth")).arg(placeHolder_Counts).arg(showColoredAmount(amountNetWorth, netWorth.isNegative()));
d->m_html += "</tr>";
d->m_html += "</table>";
d->m_html += "</div></div>";
}
}
void KHomeView::showBudget()
{
MyMoneyFile* file = MyMoneyFile::instance();
if (file->countBudgets()) {
int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction());
bool isOverrun = false;
int i = 0;
//config report just like "Monthly Budgeted vs Actual
MyMoneyReport reportCfg = MyMoneyReport(
MyMoneyReport::eBudgetActual,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::currentMonth,
MyMoneyReport::eDetailAll,
i18n("Monthly Budgeted vs. Actual"),
i18n("Generated Report"));
reportCfg.setBudget("Any", true);
reports::PivotTable table(reportCfg);
PivotGrid grid = table.grid();
//div header
d->m_html += "<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">" + i18n("Budget") + "</div>\n<div class=\"gap\">&nbsp;</div>\n";
//display budget summary
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
d->m_html += "<tr class=\"itemtitle\">";
d->m_html += "<td class=\"left\" colspan=\"3\">";
d->m_html += i18n("Current Month Summary");
d->m_html += "</td></tr>";
d->m_html += "<tr class=\"item\">";
d->m_html += "<td class=\"right\" width=\"50%\">";
d->m_html += i18n("Budgeted");
d->m_html += "</td>";
d->m_html += "<td class=\"right\" width=\"20%\">";
d->m_html += i18n("Actual");
d->m_html += "</td>";
d->m_html += "<td class=\"right\" width=\"20%\">";
d->m_html += i18n("Difference");
d->m_html += "</td></tr>";
d->m_html += QString("<tr class=\"row-odd\">");
MyMoneyMoney totalBudgetValue = grid.m_total[eBudget].m_total;
MyMoneyMoney totalActualValue = grid.m_total[eActual].m_total;
MyMoneyMoney totalBudgetDiffValue = grid.m_total[eBudgetDiff].m_total;
QString totalBudgetAmount = totalBudgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString totalActualAmount = totalActualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString totalBudgetDiffAmount = totalBudgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
d->m_html += QString("<td align=\"right\">%1</td>").arg(showColoredAmount(totalBudgetAmount, totalBudgetValue.isNegative()));
d->m_html += QString("<td align=\"right\">%1</td>").arg(showColoredAmount(totalActualAmount, totalActualValue.isNegative()));
d->m_html += QString("<td align=\"right\">%1</td>").arg(showColoredAmount(totalBudgetDiffAmount, totalBudgetDiffValue.isNegative()));
d->m_html += "</tr>";
d->m_html += "</table>";
//budget overrun
d->m_html += "<div class=\"gap\">&nbsp;</div>\n";
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
d->m_html += "<tr class=\"itemtitle\">";
d->m_html += "<td class=\"left\" colspan=\"4\">";
d->m_html += i18n("Budget Overruns");
d->m_html += "</td></tr>";
d->m_html += "<tr class=\"item\">";
d->m_html += "<td class=\"left\" width=\"30%\">";
d->m_html += i18n("Account");
d->m_html += "</td>";
d->m_html += "<td class=\"right\" width=\"20%\">";
d->m_html += i18n("Budgeted");
d->m_html += "</td>";
d->m_html += "<td class=\"right\" width=\"20%\">";
d->m_html += i18n("Actual");
d->m_html += "</td>";
d->m_html += "<td class=\"right\" width=\"20%\">";
d->m_html += i18n("Difference");
d->m_html += "</td></tr>";
PivotGrid::iterator it_outergroup = grid.begin();
while (it_outergroup != grid.end()) {
i = 0;
PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
while (it_innergroup != (*it_outergroup).end()) {
PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
while (it_row != (*it_innergroup).end()) {
//column number is 1 because the report includes only current month
if (it_row.value()[eBudgetDiff].value(1).isNegative()) {
//get report account to get the name later
ReportAccount rowname = it_row.key();
//write the outergroup if it is the first row of outergroup being shown
if (i == 0) {
d->m_html += "<tr style=\"font-weight:bold;\">";
d->m_html += QString("<td class=\"left\" colspan=\"4\">%1</td>").arg(KMyMoneyUtils::accountTypeToString(rowname.accountType()));
d->m_html += "</tr>";
}
d->m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd");
//get values from grid
MyMoneyMoney actualValue = it_row.value()[eActual][1];
MyMoneyMoney budgetValue = it_row.value()[eBudget][1];
MyMoneyMoney budgetDiffValue = it_row.value()[eBudgetDiff][1];
//format amounts
QString actualAmount = actualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString budgetAmount = budgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString budgetDiffAmount = budgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
//account name
d->m_html += QString("<td>") + link(VIEW_LEDGER, QString("?id=%1").arg(rowname.id())) + rowname.name() + linkend() + "</td>";
//show amounts
d->m_html += QString("<td align=\"right\">%1</td>").arg(showColoredAmount(budgetAmount, budgetValue.isNegative()));
d->m_html += QString("<td align=\"right\">%1</td>").arg(showColoredAmount(actualAmount, actualValue.isNegative()));
d->m_html += QString("<td align=\"right\">%1</td>").arg(showColoredAmount(budgetDiffAmount, budgetDiffValue.isNegative()));
d->m_html += "</tr>";
//set the flag that there are overruns
isOverrun = true;
}
++it_row;
}
++it_innergroup;
}
++it_outergroup;
}
//if no negative differences are found, then inform that
if (!isOverrun) {
d->m_html += QString("<tr class=\"row-%1\" style=\"font-weight:bold;\">").arg(i++ & 0x01 ? "even" : "odd");
d->m_html += QString("<td class=\"center\" colspan=\"4\">%1</td>").arg(i18n("No Budget Categories have been overrun"));
d->m_html += "</tr>";
}
d->m_html += "</table></div></div>";
}
}
QString KHomeView::showColoredAmount(const QString& amount, bool isNegative)
{
if (isNegative) {
//if negative, get the settings for negative numbers
return QString("<font color=\"%1\">%2</font>").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name(), amount);
}
//if positive, return the same string
return amount;
}
void KHomeView::doForecast()
{
//clear m_accountList because forecast is about to changed
d->m_accountList.clear();
//reinitialize the object
d->m_forecast = KMyMoneyGlobalSettings::forecast();
//If forecastDays lower than accountsCycle, adjust to the first cycle
if (d->m_forecast.accountsCycle() > d->m_forecast.forecastDays())
d->m_forecast.setForecastDays(d->m_forecast.accountsCycle());
//Get all accounts of the right type to calculate forecast
d->m_forecast.doForecast();
}
MyMoneyMoney KHomeView::forecastPaymentBalance(const MyMoneyAccount& acc, const MyMoneyMoney& payment, QDate& paymentDate)
{
//if paymentDate before or equal to currentDate set it to current date plus 1
//so we get to accumulate forecast balance correctly
if (paymentDate <= QDate::currentDate())
paymentDate = QDate::currentDate().addDays(1);
//check if the account is already there
if (d->m_accountList.find(acc.id()) == d->m_accountList.end()
|| d->m_accountList[acc.id()].find(paymentDate) == d->m_accountList[acc.id()].end()) {
if (paymentDate == QDate::currentDate()) {
d->m_accountList[acc.id()][paymentDate] = d->m_forecast.forecastBalance(acc, paymentDate);
} else {
d->m_accountList[acc.id()][paymentDate] = d->m_forecast.forecastBalance(acc, paymentDate.addDays(-1));
}
}
d->m_accountList[acc.id()][paymentDate] = d->m_accountList[acc.id()][paymentDate] + payment;
return d->m_accountList[acc.id()][paymentDate];
}
void KHomeView::showCashFlowSummary()
{
MyMoneyTransactionFilter filter;
MyMoneyMoney incomeValue;
MyMoneyMoney expenseValue;
MyMoneyFile* file = MyMoneyFile::instance();
int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction());
//set start and end of month dates
QDate startOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), 1);
QDate endOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().daysInMonth());
//Add total income and expenses for this month
//get transactions for current month
filter.setDateFilter(startOfMonth, endOfMonth);
filter.setReportAllSplits(false);
QList<MyMoneyTransaction> transactions = file->transactionList(filter);
//if no transaction then skip and print total in zero
if (transactions.size() > 0) {
QList<MyMoneyTransaction>::const_iterator it_transaction;
//get all transactions for this month
for (it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) {
//get the splits for each transaction
const QList<MyMoneySplit>& splits = (*it_transaction).splits();
QList<MyMoneySplit>::const_iterator it_split;
for (it_split = splits.begin(); it_split != splits.end(); ++it_split) {
if (!(*it_split).shares().isZero()) {
ReportAccount repSplitAcc = ReportAccount((*it_split).accountId());
//only add if it is an income or expense
if (repSplitAcc.isIncomeExpense()) {
MyMoneyMoney value;
//convert to base currency if necessary
if (repSplitAcc.currencyId() != file->baseCurrency().id()) {
MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice((*it_transaction).postDate());
value = ((*it_split).shares() * MyMoneyMoney::MINUS_ONE) * curPrice;
value = value.convert(10000);
} else {
value = ((*it_split).shares() * MyMoneyMoney::MINUS_ONE);
}
//store depending on account type
- if (repSplitAcc.accountType() == MyMoneyAccount::Income) {
+ if (repSplitAcc.accountType() == Account::Income) {
incomeValue += value;
} else {
expenseValue += value;
}
}
}
}
}
}
//format income and expenses
QString amountIncome = incomeValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString amountExpense = expenseValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
amountIncome.replace(QChar(' '), "&nbsp;");
amountExpense.replace(QChar(' '), "&nbsp;");
//calculate schedules
//Add all schedules for this month
MyMoneyMoney scheduledIncome;
MyMoneyMoney scheduledExpense;
MyMoneyMoney scheduledLiquidTransfer;
MyMoneyMoney scheduledOtherTransfer;
//get overdues and schedules until the end of this month
- QList<MyMoneySchedule> schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY,
- MyMoneySchedule::OCCUR_ANY,
- MyMoneySchedule::STYPE_ANY,
+ QList<MyMoneySchedule> schedule = file->scheduleList(QString(), Schedule::Type::Any,
+ Schedule::Occurrence::Any,
+ Schedule::PaymentType::Any,
QDate(),
- endOfMonth);
+ endOfMonth, false);
//Remove the finished schedules
QList<MyMoneySchedule>::Iterator finished_it;
for (finished_it = schedule.begin(); finished_it != schedule.end();) {
if ((*finished_it).isFinished()) {
finished_it = schedule.erase(finished_it);
continue;
}
++finished_it;
}
//add income and expenses
QList<MyMoneySchedule>::Iterator sched_it;
for (sched_it = schedule.begin(); sched_it != schedule.end();) {
QDate nextDate = (*sched_it).nextDueDate();
int cnt = 0;
while (nextDate.isValid() && nextDate <= endOfMonth) {
++cnt;
nextDate = (*sched_it).nextPayment(nextDate);
// for single occurrence nextDate will not change, so we
// better get out of here.
- if ((*sched_it).occurrence() == MyMoneySchedule::OCCUR_ONCE)
+ if ((*sched_it).occurrence() == Schedule::Occurrence::Once)
break;
}
MyMoneyAccount acc = (*sched_it).account();
if (!acc.id().isEmpty()) {
MyMoneyTransaction transaction = (*sched_it).transaction();
// only show the entry, if it is still active
MyMoneySplit sp = transaction.splitByAccount(acc.id(), true);
// take care of the autoCalc stuff
- if ((*sched_it).type() == MyMoneySchedule::TYPE_LOANPAYMENT) {
+ if ((*sched_it).type() == Schedule::Type::LoanPayment) {
QDate nextDate = (*sched_it).nextPayment((*sched_it).lastPayment());
//make sure we have all 'starting balances' so that the autocalc works
QList<MyMoneySplit>::const_iterator it_s;
QMap<QString, MyMoneyMoney> balanceMap;
for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) {
MyMoneyAccount acc = file->account((*it_s).accountId());
// collect all overdues on the first day
QDate schedDate = nextDate;
if (QDate::currentDate() >= nextDate)
schedDate = QDate::currentDate().addDays(1);
balanceMap[acc.id()] += file->balance(acc.id(), QDate::currentDate());
}
KMyMoneyUtils::calculateAutoLoan(*sched_it, transaction, balanceMap);
}
//go through the splits and assign to liquid or other transfers
const QList<MyMoneySplit> splits = transaction.splits();
QList<MyMoneySplit>::const_iterator split_it;
for (split_it = splits.constBegin(); split_it != splits.constEnd(); ++split_it) {
if ((*split_it).accountId() != acc.id()) {
ReportAccount repSplitAcc = ReportAccount((*split_it).accountId());
//get the shares and multiply by the quantity of occurrences in the period
MyMoneyMoney value = (*split_it).shares() * cnt;
//convert to foreign currency if needed
if (repSplitAcc.currencyId() != file->baseCurrency().id()) {
MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice(QDate::currentDate());
value = value * curPrice;
value = value.convert(10000);
}
if ((repSplitAcc.isLiquidLiability()
|| repSplitAcc.isLiquidAsset())
&& acc.accountGroup() != repSplitAcc.accountGroup()) {
scheduledLiquidTransfer += value;
} else if (repSplitAcc.isAssetLiability()
&& !repSplitAcc.isLiquidLiability()
&& !repSplitAcc.isLiquidAsset()) {
scheduledOtherTransfer += value;
} else if (repSplitAcc.isIncomeExpense()) {
//income and expenses are stored as negative values
- if (repSplitAcc.accountType() == MyMoneyAccount::Income)
+ if (repSplitAcc.accountType() == Account::Income)
scheduledIncome -= value;
- if (repSplitAcc.accountType() == MyMoneyAccount::Expense)
+ if (repSplitAcc.accountType() == Account::Expense)
scheduledExpense -= value;
}
}
}
}
++sched_it;
}
//format the currency strings
QString amountScheduledIncome = scheduledIncome.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString amountScheduledExpense = scheduledExpense.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString amountScheduledLiquidTransfer = scheduledLiquidTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString amountScheduledOtherTransfer = scheduledOtherTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec);
amountScheduledIncome.replace(QChar(' '), "&nbsp;");
amountScheduledExpense.replace(QChar(' '), "&nbsp;");
amountScheduledLiquidTransfer.replace(QChar(' '), "&nbsp;");
amountScheduledOtherTransfer.replace(QChar(' '), "&nbsp;");
//get liquid assets and liabilities
QList<MyMoneyAccount> accounts;
QList<MyMoneyAccount>::const_iterator account_it;
MyMoneyMoney liquidAssets;
MyMoneyMoney liquidLiabilities;
// get list of all accounts
file->accountList(accounts);
for (account_it = accounts.constBegin(); account_it != accounts.constEnd();) {
if (!(*account_it).isClosed()) {
switch ((*account_it).accountType()) {
//group all assets into one list
- case MyMoneyAccount::Checkings:
- case MyMoneyAccount::Savings:
- case MyMoneyAccount::Cash: {
+ case Account::Checkings:
+ case Account::Savings:
+ case Account::Cash: {
MyMoneyMoney value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate());
//calculate balance for foreign currency accounts
if ((*account_it).currencyId() != file->baseCurrency().id()) {
ReportAccount repAcc = ReportAccount((*account_it).id());
MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate());
MyMoneyMoney baseValue = value * curPrice;
liquidAssets += baseValue;
liquidAssets = liquidAssets.convert(10000);
} else {
liquidAssets += value;
}
break;
}
//group the liabilities into the other
- case MyMoneyAccount::CreditCard: {
+ case Account::CreditCard: {
MyMoneyMoney value;
value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate());
//calculate balance if foreign currency
if ((*account_it).currencyId() != file->baseCurrency().id()) {
ReportAccount repAcc = ReportAccount((*account_it).id());
MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate());
MyMoneyMoney baseValue = value * curPrice;
liquidLiabilities += baseValue;
liquidLiabilities = liquidLiabilities.convert(10000);
} else {
liquidLiabilities += value;
}
break;
}
default:
break;
}
}
++account_it;
}
//calculate net worth
MyMoneyMoney liquidWorth = liquidAssets + liquidLiabilities;
//format assets, liabilities and net worth
QString amountLiquidAssets = liquidAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString amountLiquidLiabilities = liquidLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString amountLiquidWorth = liquidWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec);
amountLiquidAssets.replace(QChar(' '), "&nbsp;");
amountLiquidLiabilities.replace(QChar(' '), "&nbsp;");
amountLiquidWorth.replace(QChar(' '), "&nbsp;");
//show the summary
d->m_html += "<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">" + i18n("Cash Flow Summary") + "</div>\n<div class=\"gap\">&nbsp;</div>\n";
//print header
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
//income and expense title
d->m_html += "<tr class=\"itemtitle\">";
d->m_html += "<td class=\"left\" colspan=\"4\">";
d->m_html += i18n("Income and Expenses of Current Month");
d->m_html += "</td></tr>";
//column titles
d->m_html += "<tr class=\"item\">";
d->m_html += "<td width=\"25%\" class=\"center\">";
d->m_html += i18n("Income");
d->m_html += "</td>";
d->m_html += "<td width=\"25%\" class=\"center\">";
d->m_html += i18n("Scheduled Income");
d->m_html += "</td>";
d->m_html += "<td width=\"25%\" class=\"center\">";
d->m_html += i18n("Expenses");
d->m_html += "</td>";
d->m_html += "<td width=\"25%\" class=\"center\">";
d->m_html += i18n("Scheduled Expenses");
d->m_html += "</td>";
d->m_html += "</tr>";
//add row with banding
d->m_html += QString("<tr class=\"row-even\" style=\"font-weight:bold;\">");
//print current income
d->m_html += QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountIncome, incomeValue.isNegative()));
//print the scheduled income
d->m_html += QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountScheduledIncome, scheduledIncome.isNegative()));
//print current expenses
d->m_html += QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountExpense, expenseValue.isNegative()));
//print the scheduled expenses
d->m_html += QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountScheduledExpense, scheduledExpense.isNegative()));
d->m_html += "</tr>";
d->m_html += "</table>";
//print header of assets and liabilities
d->m_html += "<div class=\"gap\">&nbsp;</div>\n";
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
//assets and liabilities title
d->m_html += "<tr class=\"itemtitle\">";
d->m_html += "<td class=\"left\" colspan=\"4\">";
d->m_html += i18n("Liquid Assets and Liabilities");
d->m_html += "</td></tr>";
//column titles
d->m_html += "<tr class=\"item\">";
d->m_html += "<td width=\"25%\" class=\"center\">";
d->m_html += i18n("Liquid Assets");
d->m_html += "</td>";
d->m_html += "<td width=\"25%\" class=\"center\">";
d->m_html += i18n("Transfers to Liquid Liabilities");
d->m_html += "</td>";
d->m_html += "<td width=\"25%\" class=\"center\">";
d->m_html += i18n("Liquid Liabilities");
d->m_html += "</td>";
d->m_html += "<td width=\"25%\" class=\"center\">";
d->m_html += i18n("Other Transfers");
d->m_html += "</td>";
d->m_html += "</tr>";
//add row with banding
d->m_html += QString("<tr class=\"row-even\" style=\"font-weight:bold;\">");
//print current liquid assets
d->m_html += QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountLiquidAssets, liquidAssets.isNegative()));
//print the scheduled transfers
d->m_html += QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountScheduledLiquidTransfer, scheduledLiquidTransfer.isNegative()));
//print current liabilities
d->m_html += QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountLiquidLiabilities, liquidLiabilities.isNegative()));
//print the scheduled transfers
d->m_html += QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountScheduledOtherTransfer, scheduledOtherTransfer.isNegative()));
d->m_html += "</tr>";
d->m_html += "</table>";
//final conclusion
MyMoneyMoney profitValue = incomeValue + expenseValue + scheduledIncome + scheduledExpense;
MyMoneyMoney expectedAsset = liquidAssets + scheduledIncome + scheduledExpense + scheduledLiquidTransfer + scheduledOtherTransfer;
MyMoneyMoney expectedLiabilities = liquidLiabilities + scheduledLiquidTransfer;
QString amountExpectedAsset = expectedAsset.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString amountExpectedLiabilities = expectedLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec);
QString amountProfit = profitValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
amountProfit.replace(QChar(' '), "&nbsp;");
amountExpectedAsset.replace(QChar(' '), "&nbsp;");
amountExpectedLiabilities.replace(QChar(' '), "&nbsp;");
//print header of cash flow status
d->m_html += "<div class=\"gap\">&nbsp;</div>\n";
d->m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
//income and expense title
d->m_html += "<tr class=\"itemtitle\">";
d->m_html += "<td class=\"left\" colspan=\"4\">";
d->m_html += i18n("Cash Flow Status");
d->m_html += "</td></tr>";
//column titles
d->m_html += "<tr class=\"item\">";
d->m_html += "<td>&nbsp;</td>";
d->m_html += "<td width=\"25%\" class=\"center\">";
d->m_html += i18n("Expected Liquid Assets");
d->m_html += "</td>";
d->m_html += "<td width=\"25%\" class=\"center\">";
d->m_html += i18n("Expected Liquid Liabilities");
d->m_html += "</td>";
d->m_html += "<td width=\"25%\" class=\"center\">";
d->m_html += i18n("Expected Profit/Loss");
d->m_html += "</td>";
d->m_html += "</tr>";
//add row with banding
d->m_html += QString("<tr class=\"row-even\" style=\"font-weight:bold;\">");
d->m_html += "<td>&nbsp;</td>";
//print expected assets
d->m_html += QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountExpectedAsset, expectedAsset.isNegative()));
//print expected liabilities
d->m_html += QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountExpectedLiabilities, expectedLiabilities.isNegative()));
//print expected profit
d->m_html += QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountProfit, profitValue.isNegative()));
d->m_html += "</tr>";
d->m_html += "</table>";
d->m_html += "</div></div>";
}
// Make sure, that these definitions are only used within this file
// this does not seem to be necessary, but when building RPMs the
// build option 'final' is used and all CPP files are concatenated.
// So it could well be, that in another CPP file these definitions
// are also used.
#undef VIEW_LEDGER
#undef VIEW_SCHEDULE
#undef VIEW_WELCOME
#undef VIEW_HOME
#undef VIEW_REPORTS
diff --git a/kmymoney/views/kinvestmentview.cpp b/kmymoney/views/kinvestmentview.cpp
index fe99e8f61..5388384e2 100644
--- a/kmymoney/views/kinvestmentview.cpp
+++ b/kmymoney/views/kinvestmentview.cpp
@@ -1,393 +1,393 @@
/***************************************************************************
kinvestmentview.cpp - description
-------------------
begin : Mon Mar 12 2007
copyright : (C) 2007 by Thomas Baumgart
email : Thomas Baumgart <ipwizard@users.sourceforge.net>
(C) 2017 Łukasz Wojniłowicz <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 "kinvestmentview.h"
#include <typeinfo>
// ----------------------------------------------------------------------------
// QT Includes
#include <QIcon>
#include <QTimer>
#include <QBitArray>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KSharedConfig>
#include <KActionCollection>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
#include "mymoneysecurity.h"
#include "mymoneyaccount.h"
#include "kmymoneyglobalsettings.h"
#include "kmymoneyaccountcombo.h"
#include "knewinvestmentwizard.h"
#include "kmymoney.h"
#include "kmymoneyview.h"
#include "accountsmodel.h"
#include "models.h"
#include "equitiesmodel.h"
#include "securitiesmodel.h"
#include "icons.h"
#include "storageenums.h"
using namespace Icons;
class KInvestmentView::Private
{
public:
Private() :
m_idInvAcc(QString()),
m_accountsProxyModel(nullptr),
m_equitiesProxyModel(nullptr),
m_securitiesProxyModel(nullptr) {}
QString m_idInvAcc;
bool m_needReload[2];
AccountNamesFilterProxyModel *m_accountsProxyModel;
EquitiesFilterProxyModel *m_equitiesProxyModel;
SecuritiesFilterProxyModel *m_securitiesProxyModel;
};
KInvestmentView::KInvestmentView(KMyMoneyApp *kmymoney, KMyMoneyView *kmymoneyview) :
QWidget(nullptr),
d(new Private),
m_kmymoney(kmymoney),
m_kmymoneyview(kmymoneyview),
m_needLoad(true)
{
}
KInvestmentView::~KInvestmentView()
{
if (!m_needLoad) {
// save the header state of the equities list
auto cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Equities");
auto cfgHeader = m_equitiesTree->header()->saveState();
auto visEColumns = d->m_equitiesProxyModel->getVisibleColumns();
QList<int> cfgColumns;
foreach (const auto visColumn, visEColumns)
cfgColumns.append(static_cast<int>(visColumn));
cfgGroup.writeEntry("HeaderState", cfgHeader);
cfgGroup.writeEntry("ColumnsSelection", cfgColumns);
// save the header state of the securities list
cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Securities");
cfgHeader = m_securitiesTree->header()->saveState();
auto visSColumns = d->m_securitiesProxyModel->getVisibleColumns();
cfgColumns.clear();
foreach (const auto visColumn, visSColumns)
cfgColumns.append(static_cast<int>(visColumn));
cfgGroup.writeEntry("HeaderState", cfgHeader);
cfgGroup.writeEntry("ColumnsSelection", cfgColumns);
}
delete d;
}
void KInvestmentView::setDefaultFocus()
{
auto tab = static_cast<Tab>(m_tab->currentIndex());
switch (tab) {
case Tab::Equities:
QTimer::singleShot(0, m_equitiesTree, SLOT(setFocus()));
break;
case Tab::Securities:
QTimer::singleShot(0, m_securitiesTree, SLOT(setFocus()));
break;
}
}
void KInvestmentView::init()
{
m_needLoad = false;
setupUi(this);
// Equities tab
d->m_accountsProxyModel = new AccountNamesFilterProxyModel(this);
- d->m_accountsProxyModel->addAccountType(MyMoneyAccount::Investment);
+ d->m_accountsProxyModel->addAccountType(eMyMoney::Account::Investment);
d->m_accountsProxyModel->setHideEquityAccounts(false);
auto const model = Models::instance()->accountsModel();
d->m_accountsProxyModel->setSourceModel(model);
d->m_accountsProxyModel->setSourceColumns(model->getColumns());
d->m_accountsProxyModel->sort((int)eAccountsModel::Column::Account);
m_accountComboBox->setModel(d->m_accountsProxyModel);
m_accountComboBox->expandAll();
auto cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Equities");
auto cfgHeader = cfgGroup.readEntry("HeaderState", QByteArray());
auto cfgColumns = cfgGroup.readEntry("ColumnsSelection", QList<int>());
QList<EquitiesModel::Column> visEColumns {EquitiesModel::Equity};
foreach (const auto cfgColumn, cfgColumns) {
const auto visColumn = static_cast<EquitiesModel::Column>(cfgColumn);
if (!visEColumns.contains(visColumn))
visEColumns.append(visColumn);
}
d->m_equitiesProxyModel = new EquitiesFilterProxyModel(this, Models::instance()->equitiesModel(), visEColumns);
m_equitiesTree->setModel(d->m_equitiesProxyModel);
m_equitiesTree->header()->restoreState(cfgHeader);
connect(m_equitiesTree, &QWidget::customContextMenuRequested, this, &KInvestmentView::slotEquityRightClicked);
// connect(m_equitiesTree->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &KInvestmentView::slotEquitySelected);
connect(m_equitiesTree, &QTreeView::doubleClicked, this, &KInvestmentView::slotEquityDoubleClicked);
m_equitiesTree->header()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_equitiesTree->header(), &QWidget::customContextMenuRequested, d->m_equitiesProxyModel, &EquitiesFilterProxyModel::slotColumnsMenu);
connect(m_accountComboBox, SIGNAL(accountSelected(QString)),
this, SLOT(slotLoadAccount(QString)));
// Securities tab
cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Securities");
cfgHeader = cfgGroup.readEntry("HeaderState", QByteArray());
cfgColumns = cfgGroup.readEntry("ColumnsSelection", QList<int>());
QList<SecuritiesModel::Column> visSColumns {SecuritiesModel::Security};
foreach (const auto cfgColumn, cfgColumns) {
const auto visColumn = static_cast<SecuritiesModel::Column>(cfgColumn);
if (!visSColumns.contains(visColumn))
visSColumns.append(visColumn);
}
d->m_securitiesProxyModel = new SecuritiesFilterProxyModel(this, Models::instance()->securitiesModel(), visSColumns);
m_securitiesTree->setModel(d->m_securitiesProxyModel);
m_securitiesTree->header()->restoreState(cfgHeader);
m_searchSecurities->setProxy(d->m_securitiesProxyModel);
m_deleteSecurityButton->setIcon(QIcon::fromTheme(g_Icons[Icon::EditDelete]));
m_editSecurityButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentEdit]));
connect(m_securitiesTree->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &KInvestmentView::slotSecuritySelected);
connect(m_editSecurityButton, &QAbstractButton::clicked, this, &KInvestmentView::slotEditSecurity);
connect(m_deleteSecurityButton, &QAbstractButton::clicked, this, &KInvestmentView::slotDeleteSecurity);
m_securitiesTree->header()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_securitiesTree->header(), &QWidget::customContextMenuRequested, d->m_securitiesProxyModel, &SecuritiesFilterProxyModel::slotColumnsMenu);
// Investment Page
d->m_needReload[Tab::Equities] = d->m_needReload[Tab::Securities] = true;
connect(m_tab, &QTabWidget::currentChanged, this, &KInvestmentView::slotLoadTab);
connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KInvestmentView::slotLoadView);
connect(this, SIGNAL(accountSelected(MyMoneyObject)), m_kmymoney, SLOT(slotSelectAccount(MyMoneyObject)));
connect(this, &KInvestmentView::equityRightClicked, m_kmymoney, &KMyMoneyApp::slotShowInvestmentContextMenu);
connect(this, &KInvestmentView::aboutToShow, m_kmymoneyview, &KMyMoneyView::aboutToChangeView);
}
void KInvestmentView::slotLoadTab(int index)
{
auto tab = static_cast<Tab>(index);
if (d->m_needReload[tab]) {
switch (tab) {
case Tab::Equities:
loadInvestmentTab();
break;
case Tab::Securities:
loadSecuritiesTab();
break;
}
d->m_needReload[tab] = false;
}
}
void KInvestmentView::slotEquitySelected(const QModelIndex &current, const QModelIndex &previous)
{
Q_UNUSED(current);
Q_UNUSED(previous);
MyMoneyAccount acc;
auto treeItem = m_equitiesTree->currentIndex();
if (treeItem.isValid()) {
auto mdlItem = d->m_equitiesProxyModel->index(treeItem.row(), EquitiesModel::Equity, treeItem.parent());
acc = MyMoneyFile::instance()->account(mdlItem.data(EquitiesModel::EquityID).toString());
}
m_kmymoney->slotSelectInvestment(acc);
}
void KInvestmentView::slotEquityRightClicked(const QPoint&)
{
slotEquitySelected(QModelIndex(), QModelIndex());
emit equityRightClicked();
}
void KInvestmentView::slotEquityDoubleClicked()
{
slotEquitySelected(QModelIndex(), QModelIndex());
m_kmymoney->actionCollection()->action(m_kmymoney->s_Actions[Action::InvestmentEdit])->trigger();
}
void KInvestmentView::slotSecuritySelected(const QModelIndex &current, const QModelIndex &previous)
{
Q_UNUSED(current);
Q_UNUSED(previous);
const auto sec = currentSecurity();
if (!sec.id().isEmpty()) {
QBitArray skip((int)eStorage::Reference::Count);
skip.fill(false);
skip.setBit((int)eStorage::Reference::Price);
m_editSecurityButton->setEnabled(true);
m_deleteSecurityButton->setEnabled(!MyMoneyFile::instance()->isReferenced(sec, skip));
} else {
m_editSecurityButton->setEnabled(false);
m_deleteSecurityButton->setEnabled(false);
}
}
void KInvestmentView::slotLoadView()
{
d->m_needReload[Tab::Equities] = d->m_needReload[Tab::Securities] = true;
if (isVisible())
slotLoadTab(m_tab->currentIndex());
}
void KInvestmentView::selectDefaultInvestmentAccount()
{
if (d->m_accountsProxyModel->rowCount() > 0) {
auto firsitem = d->m_accountsProxyModel->index(0, 0, QModelIndex());
if (d->m_accountsProxyModel->hasChildren(firsitem)) {
auto seconditem = d->m_accountsProxyModel->index(0, 0, firsitem);
slotSelectAccount(seconditem.data(EquitiesModel::EquityID).toString());
}
}
}
void KInvestmentView::slotSelectAccount(const QString &id)
{
if (!id.isEmpty()) {
d->m_idInvAcc = id;
if (isVisible())
m_accountComboBox->setSelected(id);
}
}
void KInvestmentView::slotSelectAccount(const MyMoneyObject &obj)
{
if (typeid(obj) != typeid(MyMoneyAccount))
return;
const auto acc = dynamic_cast<const MyMoneyAccount &>(obj);
- if (acc.accountType() == MyMoneyAccount::Investment)
+ if (acc.accountType() == eMyMoney::Account::Investment)
slotSelectAccount(acc.id());
}
void KInvestmentView::slotLoadAccount(const QString &id)
{
const auto indexList = d->m_equitiesProxyModel->match(d->m_equitiesProxyModel->index(0,0), EquitiesModel::InvestmentID, id, 1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive | Qt::MatchWrap));
if (!indexList.isEmpty()) {
m_equitiesTree->setRootIndex(indexList.first());
d->m_idInvAcc = id;
if (isVisible())
emit accountSelected(MyMoneyFile::instance()->account(id));
}
}
void KInvestmentView::loadInvestmentTab()
{
d->m_equitiesProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !m_kmymoney->isActionToggled(Action::ViewShowAll));
d->m_equitiesProxyModel->setHideZeroBalanceAccounts(KMyMoneyGlobalSettings::hideZeroBalanceEquities());
d->m_equitiesProxyModel->invalidate();
d->m_accountsProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !m_kmymoney->isActionToggled(Action::ViewShowAll));
d->m_accountsProxyModel->invalidate();
if (!d->m_idInvAcc.isEmpty()) { // check if account to be selected exist
try { // it could not exist anymore (e.g. another file has been opened)
const auto acc = MyMoneyFile::instance()->account(d->m_idInvAcc); // then this should throw an exception
- if (acc.accountType() == MyMoneyAccount::Investment) // it could be that id exists but account in new file isn't investment account anymore
+ if (acc.accountType() == eMyMoney::Account::Investment) // it could be that id exists but account in new file isn't investment account anymore
slotSelectAccount(d->m_idInvAcc); // otherwise select preset account
else
d->m_idInvAcc.clear();
} catch (const MyMoneyException &) {
d->m_idInvAcc.clear(); // account is invalid
}
}
if (d->m_idInvAcc.isEmpty()) // if account is invalid select default one
selectDefaultInvestmentAccount();
m_accountComboBox->expandAll();
}
void KInvestmentView::showEvent(QShowEvent* event)
{
if (m_needLoad)
init();
emit aboutToShow();
d->m_needReload[Tab::Equities] = true; // ensure tree view will be reloaded after selecting account in ledger view
slotLoadTab(m_tab->currentIndex());
// don't forget base class implementation
QWidget::showEvent(event);
}
void KInvestmentView::loadSecuritiesTab()
{
m_deleteSecurityButton->setEnabled(false);
m_editSecurityButton->setEnabled(false);
d->m_securitiesProxyModel->invalidate();
// securities model contains both securities and currencies, so...
// ...search here for securities node and show only this
const auto indexList = d->m_securitiesProxyModel->match(d->m_securitiesProxyModel->index(0, 0), Qt::DisplayRole, QLatin1String("Securities"), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
if (!indexList.isEmpty())
m_securitiesTree->setRootIndex(indexList.first());
}
void KInvestmentView::slotEditSecurity()
{
auto sec = currentSecurity();
if (!sec.id().isEmpty()) {
QPointer<KNewInvestmentWizard> dlg = new KNewInvestmentWizard(sec, this);
dlg->setObjectName("KNewInvestmentWizard");
if (dlg->exec() == QDialog::Accepted)
dlg->createObjects(QString());
delete dlg;
}
}
MyMoneySecurity KInvestmentView::currentSecurity()
{
MyMoneySecurity sec;
auto treeItem = m_securitiesTree->currentIndex();
if (treeItem.isValid()) {
auto mdlItem = d->m_securitiesProxyModel->index(treeItem.row(), SecuritiesModel::Security, treeItem.parent());
try {
sec = MyMoneyFile::instance()->security(mdlItem.data(Qt::UserRole).toString());
} catch (const MyMoneyException &) {}
}
return sec;
}
void KInvestmentView::slotDeleteSecurity()
{
auto sec = currentSecurity();
if (!sec.id().isEmpty())
KMyMoneyUtils::deleteSecurity(sec, this);
}
diff --git a/kmymoney/views/kmymoneyaccountsviewbase_p.h b/kmymoney/views/kmymoneyaccountsviewbase_p.h
index 641652fdc..4b9b3ea44 100644
--- a/kmymoney/views/kmymoneyaccountsviewbase_p.h
+++ b/kmymoney/views/kmymoneyaccountsviewbase_p.h
@@ -1,95 +1,97 @@
/***************************************************************************
kmymoneyaccountsviewbase_p.h
-------------------
copyright : (C) 2000-2001 by Michael Edwardes <mte@users.sourceforge.net>
2004 by Thomas Baumgart <ipwizard@users.sourceforge.net>
2017 by Łukasz Wojniłowicz <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. *
* *
***************************************************************************/
// ----------------------------------------------------------------------------
// QT Includes
#include <QLabel>
// ----------------------------------------------------------------------------
// KDE Includes
#include "KLocalizedString"
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyutils.h"
#include "kmymoneyviewbase_p.h"
#include "kmymoneyglobalsettings.h"
#include "mymoneyfile.h"
+#include "mymoneymoney.h"
+#include "mymoneysecurity.h"
#include "viewenums.h"
class AccountsViewProxyModel;
class KMyMoneyAccountTreeView;
class KMyMoneyAccountsViewBasePrivate : public KMyMoneyViewBasePrivate
{
public:
KMyMoneyAccountsViewBasePrivate() :
m_proxyModel(nullptr),
m_accountTree(nullptr)
{
}
~KMyMoneyAccountsViewBasePrivate()
{
}
void netBalProChanged(const MyMoneyMoney &val, QLabel *label, const View view)
{
QString s;
const auto isNegative = val.isNegative();
switch (view) {
case View::Institutions:
case View::Accounts:
s = i18n("Net Worth: ");
break;
case View::Categories:
if (isNegative)
s = i18n("Loss: ");
else
s = i18n("Profit: ");
break;
case View::Budget:
s = (i18nc("The balance of the selected budget", "Balance: "));
break;
default:
return;
}
// FIXME figure out how to deal with the approximate
// if(!(file->totalValueValid(assetAccount.id()) & file->totalValueValid(liabilityAccount.id())))
// s += "~ ";
s.replace(QLatin1Char(' '), QLatin1String("&nbsp;"));
if (isNegative)
s.append(QLatin1String("<b><font color=\"red\">"));
const auto sec = MyMoneyFile::instance()->baseCurrency();
QString v(MyMoneyUtils::formatMoney(val, sec));
s.append((v.replace(QLatin1Char(' '), QLatin1String("&nbsp;"))));
if (isNegative)
s.append(QLatin1String("</font></b>"));
label->setFont(KMyMoneyGlobalSettings::listCellFont());
label->setText(s);
}
AccountsViewProxyModel *m_proxyModel;
KMyMoneyAccountTreeView **m_accountTree;
};
diff --git a/kmymoney/views/kmymoneyview.cpp b/kmymoney/views/kmymoneyview.cpp
index c699d3cc1..969fc6326 100644
--- a/kmymoney/views/kmymoneyview.cpp
+++ b/kmymoney/views/kmymoneyview.cpp
@@ -1,2315 +1,2318 @@
/***************************************************************************
kmymoneyview.cpp
-------------------
copyright : (C) 2000-2001 by Michael Edwardes <mte@users.sourceforge.net>
2004 by Thomas Baumgart <ipwizard@users.sourceforge.net>
2017 by Łukasz Wojniłowicz <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 "kmymoneyview.h"
// ----------------------------------------------------------------------------
// Std Includes
#include <memory>
// ----------------------------------------------------------------------------
// QT Includes
#include <QFile>
#include <QRegExp>
#include <QLayout>
#include <QList>
#include <QByteArray>
#include <QUrl>
#include <QIcon>
#include <QTemporaryFile>
#include <QUrlQuery>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KMessageBox>
#include <KTitleWidget>
#include <KCompressionDevice>
#include <KSharedConfig>
#include <KBackup>
#include <KActionCollection>
#ifdef KF5Activities_FOUND
#include <KActivities/ResourceInstance>
#endif
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneyglobalsettings.h"
#include "kmymoneytitlelabel.h"
#include <libkgpgfile/kgpgfile.h>
#include "kcurrencyeditdlg.h"
#include "mymoneyseqaccessmgr.h"
#include "mymoneydatabasemgr.h"
#include "imymoneystorageformat.h"
#include "mymoneystoragebin.h"
#include "mymoneyexception.h"
#include "mymoneystoragexml.h"
#include "mymoneystoragesql.h"
#include "mymoneygncreader.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 "simpleledgerview.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 "mymoneybudget.h"
#include "mymoneyprice.h"
#include "mymoneyschedule.h"
#include "mymoneysplit.h"
#include "mymoneyaccount.h"
#include "kmymoneyedit.h"
+#include "mymoneyfile.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<QGridLayout*>(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<KTitleWidget*>(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, SIGNAL(fileLoaded(QUrl)), this, SLOT(slotRefreshViews()));
// let the accounts model know which account is being currently reconciled
connect(this, SIGNAL(reconciliationStarts(MyMoneyAccount,QDate,MyMoneyMoney)), Models::instance()->accountsModel(), SLOT(slotReconcileAccount(MyMoneyAccount,QDate,MyMoneyMoney)));
// Page 0
m_homeView = new KHomeView();
viewFrames[View::Home] = m_model->addPage(m_homeView, i18n("Home"));
viewFrames[View::Home]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewHome]));
connect(m_homeView, SIGNAL(ledgerSelected(QString,QString)),
this, SLOT(slotLedgerSelected(QString,QString)));
connect(m_homeView, SIGNAL(scheduleSelected(QString)),
this, SLOT(slotScheduleSelected(QString)));
connect(m_homeView, SIGNAL(reportSelected(QString)),
this, SLOT(slotShowReport(QString)));
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(QIcon::fromTheme(g_Icons[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(QIcon::fromTheme(g_Icons[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(QIcon::fromTheme(g_Icons[Icon::ViewSchedules]));
connect(m_scheduledView, SIGNAL(scheduleSelected(MyMoneySchedule)), kmymoney, SLOT(slotSelectSchedule(MyMoneySchedule)));
connect(m_scheduledView, SIGNAL(openContextMenu()), kmymoney, SLOT(slotShowScheduleContextMenu()));
connect(m_scheduledView, SIGNAL(enterSchedule()), kmymoney, SLOT(slotScheduleEnter()));
connect(m_scheduledView, SIGNAL(skipSchedule()), kmymoney, SLOT(slotScheduleSkip()));
connect(m_scheduledView, SIGNAL(editSchedule()), kmymoney, SLOT(slotScheduleEdit()));
connect(m_scheduledView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView()));
// Page 4
m_categoriesView = new KCategoriesView;
viewFrames[View::Categories] = m_model->addPage(m_categoriesView, i18n("Categories"));
viewFrames[View::Categories]->setIcon(QIcon::fromTheme(g_Icons[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(QIcon::fromTheme(g_Icons[Icon::ViewTags]));
connect(kmymoney, SIGNAL(tagCreated(QString)), m_tagsView, SLOT(slotSelectTagAndTransaction(QString)));
connect(kmymoney, SIGNAL(tagRename()), m_tagsView, SLOT(slotRenameButtonCliked()));
connect(m_tagsView, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowTagContextMenu()));
connect(m_tagsView, SIGNAL(selectObjects(QList<MyMoneyTag>)), kmymoney, SLOT(slotSelectTags(QList<MyMoneyTag>)));
connect(m_tagsView, SIGNAL(transactionSelected(QString,QString)),
this, SLOT(slotLedgerSelected(QString,QString)));
connect(m_tagsView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView()));
// Page 6
m_payeesView = new KPayeesView();
viewFrames[View::Payees] = m_model->addPage(m_payeesView, i18n("Payees"));
viewFrames[View::Payees]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewPayees]));
connect(kmymoney, SIGNAL(payeeCreated(QString)), m_payeesView, SLOT(slotSelectPayeeAndTransaction(QString)));
connect(kmymoney, SIGNAL(payeeRename()), m_payeesView, SLOT(slotRenameButtonCliked()));
connect(m_payeesView, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowPayeeContextMenu()));
connect(m_payeesView, SIGNAL(selectObjects(QList<MyMoneyPayee>)), kmymoney, SLOT(slotSelectPayees(QList<MyMoneyPayee>)));
connect(m_payeesView, SIGNAL(transactionSelected(QString,QString)),
this, SLOT(slotLedgerSelected(QString,QString)));
connect(m_payeesView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView()));
// Page 7
m_ledgerView = new KGlobalLedgerView();
viewFrames[View::Ledgers] = m_model->addPage(m_ledgerView, i18n("Ledgers"));
viewFrames[View::Ledgers]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewLedgers]));
connect(m_ledgerView, SIGNAL(accountSelected(MyMoneyObject)), kmymoney, SLOT(slotSelectAccount(MyMoneyObject)));
connect(m_ledgerView, SIGNAL(openContextMenu()), kmymoney, SLOT(slotShowTransactionContextMenu()));
connect(m_ledgerView, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), kmymoney, SLOT(slotSelectTransactions(KMyMoneyRegister::SelectedTransactions)));
connect(m_ledgerView, SIGNAL(newTransaction()), kmymoney, SLOT(slotTransactionsNew()));
connect(m_ledgerView, SIGNAL(cancelOrEndEdit(bool&)), kmymoney, SLOT(slotTransactionsCancelOrEnter(bool&)));
connect(m_ledgerView, SIGNAL(startEdit()), kmymoney, SLOT(slotTransactionsEdit()));
connect(m_ledgerView, SIGNAL(endEdit()), kmymoney, SLOT(slotTransactionsEnter()));
connect(m_ledgerView, SIGNAL(toggleReconciliationFlag()), kmymoney, SLOT(slotToggleReconciliationFlag()));
connect(this, SIGNAL(reconciliationStarts(MyMoneyAccount,QDate,MyMoneyMoney)), m_ledgerView, SLOT(slotSetReconcileAccount(MyMoneyAccount,QDate,MyMoneyMoney)));
connect(kmymoney, SIGNAL(selectAllTransactions()), m_ledgerView, SLOT(slotSelectAllTransactions()));
connect(m_ledgerView, &KAccountsView::aboutToShow, this, &KMyMoneyView::resetViewSelection);
// Page 8
m_investmentView = new KInvestmentView(kmymoney, this);
viewFrames[View::Investments] = m_model->addPage(m_investmentView, i18n("Investments"));
viewFrames[View::Investments]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewInvestment]));
// Page 9
m_reportsView = new KReportsView();
viewFrames[View::Reports] = m_model->addPage(m_reportsView, i18n("Reports"));
viewFrames[View::Reports]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewReports]));
connect(m_reportsView, SIGNAL(ledgerSelected(QString,QString)),
this, SLOT(slotLedgerSelected(QString,QString)));
connect(m_reportsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection);
// Page 10
m_budgetView = new KBudgetView(kmymoney);
viewFrames[View::Budget] = m_model->addPage(m_budgetView, i18n("Budgets"));
viewFrames[View::Budget]->setIcon(QIcon::fromTheme(g_Icons[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(QIcon::fromTheme(g_Icons[Icon::ViewForecast]));
// Page 12
m_onlineJobOutboxView = new KOnlineJobOutbox();
viewFrames[View::OnlineJobOutbox] = m_model->addPage(m_onlineJobOutboxView, i18n("Outbox"));
viewFrames[View::OnlineJobOutbox]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewOutbox]));
connect(m_onlineJobOutboxView, SIGNAL(sendJobs(QList<onlineJob>)), kmymoney, SLOT(slotOnlineJobSend(QList<onlineJob>)));
connect(m_onlineJobOutboxView, SIGNAL(editJob(QString)), kmymoney, SLOT(slotEditOnlineJob(QString)));
connect(m_onlineJobOutboxView, SIGNAL(newCreditTransfer()), kmymoney, SLOT(slotNewOnlineTransfer()));
connect(m_onlineJobOutboxView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView()));
connect(m_onlineJobOutboxView, SIGNAL(showContextMenu(onlineJob)), kmymoney, SLOT(slotShowOnlineJobContextMenu()));
SimpleLedgerView* view = new SimpleLedgerView(kmymoney, this);
KPageWidgetItem* frame = m_model->addPage(view, i18n("New ledger"));
frame->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentProperties]));
//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<pageInfo> 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<View, QAction *> lutActions;
auto aC = kmymoney->actionCollection();
auto pageCount = 0;
foreach (const pageInfo info, pageInfos) {
auto a = new QAction(0);
// 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);
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<QTreeView *> views = findChildren<QTreeView*>();
foreach (QTreeView * view, views) {
if (view && (view->parent() == this)) {
view->setRootIsDecorated(false);
break;
}
}
}
}
}
void KMyMoneyView::slotAccountTreeViewChanged(const eAccountsModel::Column column, const bool show)
{
QVector<AccountsViewProxyModel *> 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 <b>%1</b> column on every loaded view?", AccountsModel::getHeaderName(column));
else
question = i18n("Do you want to hide <b>%1</b> 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();
}
}
}
bool KMyMoneyView::showPageHeader() const
{
return false;
}
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;
}
bool KMyMoneyView::canCreateTransactions(const KMyMoneyRegister::SelectedTransactions& /* list */, QString& tooltip) const
{
// we can only create transactions in the ledger view so
// we check that this is the active page
bool rc = (viewFrames[View::Ledgers] == currentPage());
if (rc)
rc = m_ledgerView->canCreateTransactions(tooltip);
else
tooltip = i18n("Creating transactions can only be performed in the ledger view");
return rc;
}
bool KMyMoneyView::canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const
{
// we can only modify transactions in the ledger view so
// we check that this is the active page
bool rc = (viewFrames[View::Ledgers] == currentPage());
if (rc) {
rc = m_ledgerView->canModifyTransactions(list, tooltip);
} else {
tooltip = i18n("Modifying transactions can only be performed in the ledger view");
}
return rc;
}
bool KMyMoneyView::canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const
{
// we can only duplicate transactions in the ledger view so
// we check that this is the active page
bool rc = (viewFrames[View::Ledgers] == currentPage());
if (rc) {
rc = m_ledgerView->canDuplicateTransactions(list, tooltip);
} else {
tooltip = i18n("Duplicating transactions can only be performed in the ledger view");
}
return rc;
}
bool KMyMoneyView::canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const
{
bool rc;
// we can only edit transactions in the ledger view so
// we check that this is the active page
if ((rc = canModifyTransactions(list, tooltip)) == true) {
tooltip = i18n("Edit the current selected transactions");
rc = m_ledgerView->canEditTransactions(list, tooltip);
}
return rc;
}
bool KMyMoneyView::createNewTransaction()
{
bool rc = false;
KMyMoneyRegister::SelectedTransactions list;
QString txt;
if (canCreateTransactions(list, txt)) {
rc = m_ledgerView->selectEmptyTransaction();
}
return rc;
}
TransactionEditor* KMyMoneyView::startEdit(const KMyMoneyRegister::SelectedTransactions& list)
{
TransactionEditor* editor = 0;
QString txt;
if (canEditTransactions(list, txt) || canCreateTransactions(list, txt)) {
editor = m_ledgerView->startEdit(list);
}
return editor;
}
void KMyMoneyView::newStorage(storageTypeE t)
{
removeStorage();
MyMoneyFile* file = MyMoneyFile::instance();
if (t == Memory)
file->attachStorage(new MyMoneySeqAccessMgr);
else
file->attachStorage(new MyMoneyDatabaseMgr);
}
void KMyMoneyView::removeStorage()
{
MyMoneyFile* file = MyMoneyFile::instance();
IMyMoneyStorage* p = file->storage();
if (p != 0) {
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::slotLedgerSelected(const QString& _accId, const QString& transaction)
{
MyMoneyAccount acc = MyMoneyFile::instance()->account(_accId);
QString accId(_accId);
switch (acc.accountType()) {
- case MyMoneyAccount::Stock:
+ case Account::Stock:
// if a stock account is selected, we show the
// the corresponding parent (investment) account
acc = MyMoneyFile::instance()->account(acc.parentAccountId());
accId = acc.id();
// intentional fall through
- case MyMoneyAccount::Checkings:
- case MyMoneyAccount::Savings:
- case MyMoneyAccount::Cash:
- case MyMoneyAccount::CreditCard:
- case MyMoneyAccount::Loan:
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::AssetLoan:
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
- case MyMoneyAccount::Investment:
- case MyMoneyAccount::Equity:
+ case Account::Checkings:
+ case Account::Savings:
+ case Account::Cash:
+ case Account::CreditCard:
+ case Account::Loan:
+ case Account::Asset:
+ case Account::Liability:
+ case Account::AssetLoan:
+ case Account::Income:
+ case Account::Expense:
+ case Account::Investment:
+ case Account::Equity:
setCurrentPage(viewFrames[View::Ledgers]);
m_ledgerView->slotSelectAccount(accId, transaction);
break;
- case MyMoneyAccount::CertificateDep:
- case MyMoneyAccount::MoneyMarket:
- case MyMoneyAccount::Currency:
- qDebug("No ledger view available for account type %d", acc.accountType());
+ case Account::CertificateDep:
+ case Account::MoneyMarket:
+ case Account::Currency:
+ qDebug("No ledger view available for account type %d", (int)acc.accountType());
break;
default:
- qDebug("Unknown account type %d in KMyMoneyView::slotLedgerSelected", acc.accountType());
+ qDebug("Unknown account type %d in KMyMoneyView::slotLedgerSelected", (int)acc.accountType());
break;
}
}
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);
}
void KMyMoneyView::slotScheduleSelected(const QString& scheduleId)
{
MyMoneySchedule sched = MyMoneyFile::instance()->schedule(scheduleId);
kmymoney->slotSelectSchedule(sched);
}
void KMyMoneyView::slotShowReport(const QString& reportid)
{
showPage(viewFrames[View::Reports]);
m_reportsView->slotOpenReport(reportid);
}
void KMyMoneyView::slotShowReport(const MyMoneyReport& report)
{
showPage(viewFrames[View::Reports]);
m_reportsView->slotOpenReport(report);
}
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::slotLoadView);
// 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)
{
QString filename;
m_fileOpen = false;
bool isEncrypted = false;
IMyMoneyStorageFormat* 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;
if (url.isLocalFile()) {
filename = url.toLocalFile();
} else {
// TODO: port to kf5 (NetAccess)
#if 0
if (!KIO::NetAccess::download(url, filename, 0)) {
KMessageBox::detailedError(this,
i18n("Error while loading file '%1'.", url.url()),
KIO::NetAccess::lastErrorString(),
i18n("File access error"));
return false;
}
#endif
}
// 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("<p><b>%1</b> is not a KMyMoney file.</p>", 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("<qt>%1</qt>"). arg(i18n("GPG is not available for decryption of file <b>%1</b>", 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. '?<xml' ).
if ((magic0 == MAGIC_0_50 && magic1 == MAGIC_0_51)
|| magic0 < 30) {
// we do not support this file format anymore
pReader = 0;
m_fileType = KmmBinary;
} else {
// Scan the first 70 bytes to see if we find something
// we know. For now, we support our own XML format and
// GNUCash XML format. If the file is smaller, then it
// contains no valid data and we reject it anyway.
hdr.resize(70);
if (qfile->read(hdr.data(), 70) == 70) {
if (haveAt)
qfile->seek(0);
else
ungetString(qfile, hdr.data(), 70);
QRegExp kmyexp("<!DOCTYPE KMYMONEY-FILE>");
QRegExp gncexp("<gnc-v(\\d+)");
QByteArray txt(hdr, 70);
if (kmyexp.indexIn(txt) != -1) {
pReader = new MyMoneyStorageXML;
m_fileType = KmmXML;
} else if (gncexp.indexIn(txt) != -1) {
MyMoneyFile::instance()->attachStorage(storage);
loadAllCurrencies(); // currency list required for gnc
MyMoneyFile::instance()->detachStorage(storage);
pReader = new MyMoneyGncReader;
m_fileType = GncXML;
}
}
}
if (pReader) {
pReader->setProgressCallback(&KMyMoneyView::progressCallback);
pReader->readFile(qfile, dynamic_cast<IMyMoneySerialize*>(storage));
} else {
if (m_fileType == KmmBinary) {
KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("File <b>%1</b> 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("<qt>%1</qt>"). arg(i18n("File <b>%1</b> contains an unknown file format.", filename)));
}
rc = false;
}
} else {
KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("Cannot read from file <b>%1</b>.", filename)));
rc = false;
}
} catch (const MyMoneyException &e) {
KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("Cannot load file <b>%1</b>. Reason: %2", filename, e.what())));
rc = false;
}
if (pReader) {
pReader->setProgressCallback(0);
delete pReader;
}
qfile->close();
} else {
KGPGFile *gpgFile = qobject_cast<KGPGFile *>(qfile);
if (gpgFile && !gpgFile->errorToString().isEmpty()) {
KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("The following error was encountered while decrypting file <b>%1</b>: %2", filename, gpgFile->errorToString())));
} else {
KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("File <b>%1</b> not found.", filename)));
}
rc = false;
}
delete qfile;
}
} else {
KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("File <b>%1</b> 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 constructed by NetAccess::download,
// then it will be removed with the next call. Otherwise, it
// stays untouched on the local filesystem
// TODO: port to KF5 (NetAccess)
//KIO::NetAccess::removeTempFile(filename);
return initializeStorage();
}
void KMyMoneyView::checkAccountName(const MyMoneyAccount& _acc, const QString& name) const
{
MyMoneyFile* 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<IMyMoneySerialize*>(MyMoneyFile::instance()->storage());
MyMoneyDatabaseMgr* pDBMgr = 0;
if (! pStorage) {
pDBMgr = new MyMoneyDatabaseMgr;
pStorage = dynamic_cast<IMyMoneySerialize*>(pDBMgr);
}
QExplicitlySharedDataPointer <MyMoneyStorageSql> reader = pStorage->connectToDatabase(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) {
removeStorage();
delete pDBMgr;
}
return false;
case -1: // retryable error
if (KMessageBox::warningYesNo(this, reader->lastError(), PACKAGE) == KMessageBox::No) {
if (pDBMgr) {
removeStorage();
delete pDBMgr;
}
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,
i18n("An unrecoverable error occurred while reading the database"),
reader->lastError().toLatin1(),
i18n("Database malfunction"));
return false;
}
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
//
// <PAIR key="kmm-baseCurrency" value="xxx" />
//
// 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<View>(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();
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::slotLoadView);
// 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)
{
// 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("<p>You have selected to encrypt your data also with the KMyMoney recover key, but the key with id</p><p><center><b>%1</b></center></p><p>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 <a href=\"https://kmymoney.org/\">KMyMoney web-site</a>. This time your data will not be encrypted with the KMyMoney recover key.</p>", 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("<p>You have specified to encrypt your data for the user-id</p><p><center><b>%1</b>.</center></p><p>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.</p>", key), i18n("GPG Key not found"));
encryptFile = false;
break;
}
}
if (encryptFile == true) {
QString msg = i18n("<p>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 <b>No</b>.</p>");
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<QIODevice> device;
if (!keyList.isEmpty() && encryptFile && !plaintext) {
std::unique_ptr<KGPGFile> kgpg = std::unique_ptr<KGPGFile>(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<decltype(device)::element_type>(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<decltype(device)::element_type>(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<IMyMoneySerialize*>(MyMoneyFile::instance()->storage()));
device->close();
// Check for errors if possible, only possible for KGPGFile
QFileDevice *fileDevice = qobject_cast<QFileDevice*>(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<IMyMoneyStorageFormat> 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<IMyMoneyStorageFormat>(new MyMoneyStorageANON);
} else {
storageWriter = std::unique_ptr<IMyMoneyStorageFormat>(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);
// TODO: port to kf5
//if (!KIO::NetAccess::upload(tmpfile.fileName(), url, 0))
// throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'", url.toDisplayString()));
}
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<IMyMoneySerialize*>(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,
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;
}
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;
} 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());
}
delete writer;
return (rc);
}
bool KMyMoneyView::dirty()
{
if (!fileOpen())
return false;
return MyMoneyFile::instance()->dirty();
}
bool KMyMoneyView::startReconciliation(const MyMoneyAccount& account, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance)
{
bool ok = true;
// we cannot reconcile standard accounts
if (MyMoneyFile::instance()->isStandardAccount(account.id()))
ok = false;
// check if we can reconcile this account
// it makes sense for asset and liability accounts only
if (ok == true) {
if (account.isAssetLiability()) {
showPage(viewFrames[View::Ledgers]);
// prepare reconciliation mode
emit reconciliationStarts(account, reconciliationDate, endingBalance);
} else {
ok = false;
}
}
return ok;
}
void KMyMoneyView::finishReconciliation(const MyMoneyAccount& /* account */)
{
emit reconciliationStarts(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()
{
MyMoneyFile* 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<KCurrencyEditDlg> 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<MyMoneyAccount> list;
file->accountList(list);
QList<MyMoneyAccount>::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()
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyFileTransaction ft;
QList<MyMoneySecurity> storedCurrencies = MyMoneyFile::instance()->currencyList();
QList<MyMoneySecurity> 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()
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyFileTransaction ft;
if (!file->currencyList().isEmpty())
return;
QMap<MyMoneySecurity, MyMoneyPrice> 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, SIGNAL(accountSelected(MyMoneyObject)), m_ledgerView, SLOT(slotSelectAccount(MyMoneyObject)));
disconnect(m_ledgerView, SIGNAL(accountSelected(MyMoneyObject)), m_investmentView, SLOT(slotSelectAccount(MyMoneyObject)));
// TODO turn sync between ledger and investment view if selected by user
if (KMyMoneyGlobalSettings::syncLedgerInvestment()) {
connect(m_investmentView, SIGNAL(accountSelected(MyMoneyObject)), m_ledgerView, SLOT(slotSelectAccount(MyMoneyObject)));
connect(m_ledgerView, SIGNAL(accountSelected(MyMoneyObject)), m_investmentView, SLOT(slotSelectAccount(MyMoneyObject)));
}
showTitleBar(KMyMoneyGlobalSettings::showTitleBar());
m_accountsView->refresh();
m_institutionsView->refresh();
m_categoriesView->refresh();
m_payeesView->slotLoadPayees();
m_tagsView->slotLoadTags();
m_ledgerView->slotLoadView();
m_budgetView->refresh();
m_homeView->slotLoadView();
m_investmentView->slotLoadView();
m_reportsView->slotLoadView();
m_forecastView->slotLoadForecast();
m_scheduledView->slotReloadView();
}
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()
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneyTransactionFilter filter;
filter.setReportAllSplits(false);
QList<MyMoneyTransaction> 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.
QList<MyMoneyTransaction>::Iterator it_t;
int count = 0;
for (it_t = transactionList.begin(); it_t != transactionList.end(); ++it_t) {
if ((*it_t).splitCount() == 2) {
QString accountId;
QString categoryId;
QString accountMemo;
QString categoryMemo;
const QList<MyMoneySplit>& splits = (*it_t).splits();
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) {
MyMoneyAccount acc = file->account((*it_s).accountId());
if (acc.isIncomeExpense()) {
categoryId = (*it_s).id();
categoryMemo = (*it_s).memo();
} else {
accountId = (*it_s).id();
accountMemo = (*it_s).memo();
}
}
if (!accountId.isEmpty() && !categoryId.isEmpty()
&& accountMemo != categoryMemo) {
MyMoneyTransaction t(*it_t);
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<MyMoneyReport> reports = MyMoneyFile::instance()->reportList();
QList<MyMoneyReport>::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) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a);
- if (acc.accountType() == MyMoneyAccount::Investment) {
+ if (acc.accountType() == Account::Investment) {
for (it_b = acc.accountList().begin(); it_b != acc.accountList().end(); ++it_b) {
if (!list.contains(*it_b)) {
missing.append(*it_b);
}
}
}
}
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) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a);
- if (acc.accountType() == MyMoneyAccount::Investment) {
+ if (acc.accountType() == Account::Investment) {
for (it_b = acc.accountList().begin(); it_b != acc.accountList().end(); ++it_b) {
if (!list.contains(*it_b)) {
missing.append(*it_b);
}
}
}
}
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.
*/
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneyAccount> accountList;
file->accountList(accountList);
QList<MyMoneyAccount>::Iterator it_a;
QList<MyMoneySchedule> scheduleList = file->scheduleList();
QList<MyMoneySchedule>::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() == MyMoneyAccount::Loan
- || (*it_a).accountType() == MyMoneyAccount::AssetLoan) {
+ if ((*it_a).accountType() == Account::Loan
+ || (*it_a).accountType() == Account::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() == MyMoneyAccount::Equity) {
+ if (equityListEmpty && (*it_a).accountType() == Account::Equity) {
if ((*it_a).parentAccountId() == equity.id()) {
MyMoneyAccount 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<MyMoneySplit> splitList = t.splits();
QList<MyMoneySplit>::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 {
MyMoneyAccount 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() != MyMoneySplit::NotReconciled) {
qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'";
MyMoneySplit split = *it_s;
split.setReconcileDate(QDate());
split.setReconcileFlag(MyMoneySplit::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("<p>The account \"%1\" was previously created as loan account but some information is missing.</p><p>The new loan wizard will be started to collect all relevant information.</p><p>Please use KMyMoney version 0.8.7 or later and earlier than version 0.9 to correct the problem.</p>"
, 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 ....
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) {
if ((*it_s).accountId().isEmpty()) {
MyMoneySplit s = (*it_s);
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()
{
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneySchedule> scheduleList = file->scheduleList();
MyMoneyTransactionFilter filter;
filter.setReportAllSplits(false);
QList<MyMoneyTransaction> transactionList;
file->transactionList(transactionList, filter);
QList<MyMoneySchedule>::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<MyMoneySplit>::ConstIterator it_s;
QStringList accounts;
bool hasDuplicateAccounts = false;
for (it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) {
if (accounts.contains((*it_s).accountId())) {
hasDuplicateAccounts = true;
qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << (*it_s).accountId();
} else {
accounts << (*it_s).accountId();
}
if ((*it_s).action() == MyMoneySplit::ActionInterest) {
if (interestAccounts.contains((*it_s).accountId()) == 0) {
interestAccounts << (*it_s).accountId();
}
}
}
if (hasDuplicateAccounts) {
fixDuplicateAccounts_0(t);
}
++cnt;
if (!(cnt % 10))
kmymoney->slotStatusProgressBar(cnt);
}
// scan the transactions and modify loan transactions
QList<MyMoneyTransaction>::Iterator it_t;
for (it_t = transactionList.begin(); it_t != transactionList.end(); ++it_t) {
const char *defaultAction = 0;
QList<MyMoneySplit> splits = (*it_t).splits();
QList<MyMoneySplit>::Iterator it_s;
QStringList accounts;
// check if base commodity is set. if not, set baseCurrency
if ((*it_t).commodity().isEmpty()) {
qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " has no base currency";
(*it_t).setCommodity(file->baseCurrency().id());
file->modifyTransaction(*it_t);
}
bool isLoan = false;
// Determine default action
if ((*it_t).splitCount() == 2) {
// check for transfer
int accountCount = 0;
MyMoneyMoney val;
for (it_s = splits.begin(); it_s != splits.end(); ++it_s) {
MyMoneyAccount acc = file->account((*it_s).accountId());
- if (acc.accountGroup() == MyMoneyAccount::Asset
- || acc.accountGroup() == MyMoneyAccount::Liability) {
+ if (acc.accountGroup() == Account::Asset
+ || acc.accountGroup() == Account::Liability) {
val = (*it_s).value();
accountCount++;
- if (acc.accountType() == MyMoneyAccount::Loan
- || acc.accountType() == MyMoneyAccount::AssetLoan)
+ if (acc.accountType() == Account::Loan
+ || acc.accountType() == Account::AssetLoan)
isLoan = true;
} else
break;
}
if (accountCount == 2) {
if (isLoan)
defaultAction = MyMoneySplit::ActionAmortization;
else
defaultAction = MyMoneySplit::ActionTransfer;
} else {
if (val.isNegative())
defaultAction = MyMoneySplit::ActionWithdrawal;
else
defaultAction = MyMoneySplit::ActionDeposit;
}
}
isLoan = false;
for (it_s = splits.begin(); defaultAction == 0 && it_s != splits.end(); ++it_s) {
MyMoneyAccount acc = file->account((*it_s).accountId());
MyMoneyMoney val = (*it_s).value();
- if (acc.accountGroup() == MyMoneyAccount::Asset
- || acc.accountGroup() == MyMoneyAccount::Liability) {
+ if (acc.accountGroup() == Account::Asset
+ || acc.accountGroup() == Account::Liability) {
if (!val.isPositive())
defaultAction = MyMoneySplit::ActionWithdrawal;
else
defaultAction = MyMoneySplit::ActionDeposit;
}
}
#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) {
MyMoneyAccount acc = file->account((*it_s).accountId());
MyMoneyMoney val = (*it_s).value();
- if (acc.accountType() == MyMoneyAccount::CreditCard) {
+ if (acc.accountType() == Account::CreditCard) {
if (val < 0 && (*it_s).action() != MyMoneySplit::ActionWithdrawal && (*it_s).action() != MyMoneySplit::ActionTransfer)
needModify = true;
if (val >= 0 && (*it_s).action() != MyMoneySplit::ActionDeposit && (*it_s).action() != MyMoneySplit::ActionTransfer)
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);
(*it_t).modifySplit(*it_s);
file->modifyTransaction(*it_t);
}
splits = (*it_t).splits(); // update local copy
qDebug("Fixed credit card assignment in %s", (*it_t).id().data());
}
#endif
// Check for correct assignment of ActionInterest in all splits
// and check if there are any duplicates in this transactions
for (it_s = splits.begin(); it_s != splits.end(); ++it_s) {
MyMoneyAccount splitAccount = file->account((*it_s).accountId());
if (!accounts.contains((*it_s).accountId())) {
accounts << (*it_s).accountId();
}
// if this split references an interest account, the action
// must be of type ActionInterest
if (interestAccounts.contains((*it_s).accountId())) {
if ((*it_s).action() != MyMoneySplit::ActionInterest) {
qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " contains an interest account (" << (*it_s).accountId() << ") but does not have ActionInterest";
(*it_s).setAction(MyMoneySplit::ActionInterest);
(*it_t).modifySplit(*it_s);
file->modifyTransaction(*it_t);
qDebug("Fixed interest action in %s", qPrintable((*it_t).id()));
}
// if it does not reference an interest account, it must not be
// of type ActionInterest
} else {
if ((*it_s).action() == MyMoneySplit::ActionInterest) {
qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " does not contain an interest account so it should not have ActionInterest";
(*it_s).setAction(defaultAction);
(*it_t).modifySplit(*it_s);
file->modifyTransaction(*it_t);
qDebug("Fixed interest action in %s", qPrintable((*it_t).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 ((*it_t).commodity() == splitAccount.currencyId()
&& (*it_s).value() != (*it_s).shares()) {
qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " " << (*it_s).id() << " uses the transaction currency, but shares != value";
(*it_s).setShares((*it_s).value());
(*it_t).modifySplit(*it_s);
file->modifyTransaction(*it_t);
}
// fix the shares and values to have the correct fraction
if (!splitAccount.isInvest()) {
try {
int fract = splitAccount.fraction();
if ((*it_s).shares() != (*it_s).shares().convert(fract)) {
qDebug("adjusting fraction in %s,%s", qPrintable((*it_t).id()), qPrintable((*it_s).id()));
(*it_s).setShares((*it_s).shares().convert(fract));
(*it_s).setValue((*it_s).value().convert(fract));
(*it_t).modifySplit(*it_s);
file->modifyTransaction(*it_t);
}
} 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::Accounts:
disconnect(m_accountsView, &KAccountsView::aboutToShow, this, &KMyMoneyView::connectView);
treeView = m_accountsView->getTreeView();
connect(treeView, &KMyMoneyAccountTreeView::selectObject, kmymoney, &KMyMoneyApp::slotSelectAccount);
connect(treeView, &KMyMoneyAccountTreeView::selectObject, kmymoney, &KMyMoneyApp::slotSelectInstitution);
connect(treeView, &KMyMoneyAccountTreeView::selectObject, kmymoney, &KMyMoneyApp::slotSelectInvestment);
connect(treeView, &KMyMoneyAccountTreeView::openObject, kmymoney, &KMyMoneyApp::slotAccountOpen);
connect(treeView, &KMyMoneyAccountTreeView::openContextMenu, kmymoney, &KMyMoneyApp::slotShowAccountContextMenu);
connect(treeView, &KMyMoneyAccountTreeView::columnToggled , this, &KMyMoneyView::slotAccountTreeViewChanged);
connect(Models::instance()->accountsModel(), &AccountsModel::netWorthChanged, m_accountsView, &KAccountsView::slotNetWorthChanged);
connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_accountsView, &KAccountsView::refresh);
break;
case View::Institutions:
disconnect(m_institutionsView, &KInstitutionsView::aboutToShow, this, &KMyMoneyView::connectView);
treeView = m_institutionsView->getTreeView();
connect(treeView, &KMyMoneyAccountTreeView::selectObject, kmymoney, &KMyMoneyApp::slotSelectAccount);
connect(treeView, &KMyMoneyAccountTreeView::selectObject, kmymoney, &KMyMoneyApp::slotSelectInstitution);
connect(treeView, &KMyMoneyAccountTreeView::openObject, kmymoney, &KMyMoneyApp::slotAccountOpen);
connect(treeView, &KMyMoneyAccountTreeView::openObject, kmymoney, &KMyMoneyApp::slotInstitutionEdit);
connect(treeView, &KMyMoneyAccountTreeView::openContextMenu, kmymoney, &KMyMoneyApp::slotShowAccountContextMenu);
connect(treeView, &KMyMoneyAccountTreeView::openContextMenu, kmymoney, &KMyMoneyApp::slotShowInstitutionContextMenu);
connect(treeView, &KMyMoneyAccountTreeView::columnToggled , this, &KMyMoneyView::slotAccountTreeViewChanged);
connect(Models::instance()->institutionsModel(), &AccountsModel::netWorthChanged, m_institutionsView, &KInstitutionsView::slotNetWorthChanged);
connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_institutionsView, &KInstitutionsView::refresh);
break;
case View::Categories:
disconnect(m_categoriesView, &KCategoriesView::aboutToShow, this, &KMyMoneyView::connectView);
treeView = m_categoriesView->getTreeView();
connect(treeView, &KMyMoneyAccountTreeView::selectObject, kmymoney, &KMyMoneyApp::slotSelectAccount);
connect(treeView, &KMyMoneyAccountTreeView::openObject, kmymoney, &KMyMoneyApp::slotAccountOpen);
connect(treeView, &KMyMoneyAccountTreeView::openContextMenu, kmymoney, &KMyMoneyApp::slotShowAccountContextMenu);
connect(treeView, &KMyMoneyAccountTreeView::columnToggled , this, &KMyMoneyView::slotAccountTreeViewChanged);
connect(Models::instance()->institutionsModel(), &AccountsModel::profitChanged, m_categoriesView, &KCategoriesView::slotProfitChanged);
connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_categoriesView, &KCategoriesView::refresh);
break;
case View::Budget:
disconnect(m_budgetView, &KBudgetView::aboutToShow, this, &KMyMoneyView::connectView);
treeView = m_budgetView->getTreeView();
connect(treeView, &KMyMoneyAccountTreeView::selectObject, kmymoney, &KMyMoneyApp::slotSelectAccount);
connect(treeView, &KMyMoneyAccountTreeView::selectObject, kmymoney, &KMyMoneyApp::slotSelectInstitution);
connect(treeView, &KMyMoneyAccountTreeView::selectObject, kmymoney, &KMyMoneyApp::slotSelectInvestment);
connect(treeView, &KMyMoneyAccountTreeView::openObject, kmymoney, &KMyMoneyApp::slotAccountOpen);
connect(treeView, &KMyMoneyAccountTreeView::openContextMenu, kmymoney, &KMyMoneyApp::slotShowAccountContextMenu);
connect(m_budgetView, &KBudgetView::openContextMenu, kmymoney, &KMyMoneyApp::slotShowBudgetContextMenu);
connect(m_budgetView, &KBudgetView::selectObjects, kmymoney, &KMyMoneyApp::slotSelectBudget);
connect(kmymoney, &KMyMoneyApp::budgetRename, m_budgetView, &KBudgetView::slotStartRename);
connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_budgetView, &KBudgetView::refresh);
connect(treeView, &KMyMoneyAccountTreeView::columnToggled , this, &KMyMoneyView::slotAccountTreeViewChanged);
break;
default:
break;
}
}
diff --git a/kmymoney/views/kpayeesview.cpp b/kmymoney/views/kpayeesview.cpp
index cba6d0106..dacb02b78 100644
--- a/kmymoney/views/kpayeesview.cpp
+++ b/kmymoney/views/kpayeesview.cpp
@@ -1,987 +1,989 @@
/***************************************************************************
kpayeesview.cpp
---------------
begin : Thu Jan 24 2002
copyright : (C) 2000-2002 by Michael Edwardes <mte@users.sourceforge.net>
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@users.sourceforge.net>
Andreas Nicolai <Andreas.Nicolai@gmx.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 "kpayeesview.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QPushButton>
#include <QComboBox>
#include <QLabel>
#include <QTabWidget>
#include <QCheckBox>
#include <QSplitter>
#include <QMap>
#include <QList>
#include <QTimer>
#include <QIcon>
#include <QDesktopServices>
#include <QHBoxLayout>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KMessageBox>
#include <KGuiItem>
#include <KHelpClient>
#include <KSharedConfig>
#include <KActionCollection>
#include <KListWidgetSearchLine>
// ----------------------------------------------------------------------------
// Project Includes
#include "config-kmymoney.h"
#include "mymoneyfile.h"
+#include "mymoneyprice.h"
+#include "mymoneytransactionfilter.h"
#include "kmymoneyglobalsettings.h"
#include "kmymoney.h"
#include "models.h"
#include "accountsmodel.h"
#include "mymoneysecurity.h"
#include "mymoneycontact.h"
#include "icons/icons.h"
using namespace Icons;
// *** KPayeeListItem Implementation ***
KPayeeListItem::KPayeeListItem(QListWidget *parent, const MyMoneyPayee& payee) :
QListWidgetItem(parent, QListWidgetItem::UserType),
m_payee(payee)
{
setText(payee.name());
// allow in column rename
setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
}
KPayeeListItem::~KPayeeListItem()
{
}
// *** KPayeesView Implementation ***
KPayeesView::KPayeesView(QWidget *parent) :
QWidget(parent),
m_contact(new MyMoneyContact(this)),
m_needReload(false),
m_needLoad(true),
m_inSelection(false),
m_allowEditing(true),
m_payeeFilterType(0)
{
}
KPayeesView::~KPayeesView()
{
if(!m_needLoad) {
// remember the splitter settings for startup
KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings");
grp.writeEntry("KPayeesViewSplitterSize", m_splitter->saveState());
grp.sync();
}
}
void KPayeesView::setDefaultFocus()
{
QTimer::singleShot(0, m_searchWidget, SLOT(setFocus()));
}
void KPayeesView::init()
{
m_needLoad = false;
setupUi(this);
m_filterProxyModel = new AccountNamesFilterProxyModel(this);
m_filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode());
- m_filterProxyModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Asset, MyMoneyAccount::Liability, MyMoneyAccount::Income, MyMoneyAccount::Expense, MyMoneyAccount::Equity});
+ m_filterProxyModel->addAccountGroup(QVector<eMyMoney::Account> {eMyMoney::Account::Asset, eMyMoney::Account::Liability, eMyMoney::Account::Income, eMyMoney::Account::Expense, eMyMoney::Account::Equity});
auto const model = Models::instance()->accountsModel();
m_filterProxyModel->setSourceModel(model);
m_filterProxyModel->setSourceColumns(model->getColumns());
m_filterProxyModel->sort((int)eAccountsModel::Column::Account);
comboDefaultCategory->setModel(m_filterProxyModel);
matchTypeCombo->addItem(i18nc("@item No matching", "No matching"), MyMoneyPayee::matchDisabled);
matchTypeCombo->addItem(i18nc("@item Match Payees name partially", "Match Payees name (partial)"), MyMoneyPayee::matchName);
matchTypeCombo->addItem(i18nc("@item Match Payees name exactly", "Match Payees name (exact)"), MyMoneyPayee::matchNameExact);
matchTypeCombo->addItem(i18nc("@item Search match in list", "Match on a name listed below"), MyMoneyPayee::matchKey);
// create the searchline widget
// and insert it into the existing layout
m_searchWidget = new KListWidgetSearchLine(this, m_payeesList);
m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
m_payeesList->setContextMenuPolicy(Qt::CustomContextMenu);
m_listTopHLayout->insertWidget(0, m_searchWidget);
//load the filter type
m_filterBox->addItem(i18nc("@item Show all payees", "All"));
m_filterBox->addItem(i18nc("@item Show only used payees", "Used"));
m_filterBox->addItem(i18nc("@item Show only unused payees", "Unused"));
m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
KGuiItem newButtonItem(QString(),
QIcon::fromTheme(g_Icons[Icon::ListAddUser]),
i18n("Creates a new payee"),
i18n("Use this to create a new payee."));
KGuiItem::assign(m_newButton, newButtonItem);
m_newButton->setToolTip(newButtonItem.toolTip());
KGuiItem renameButtonItem(QString(),
QIcon::fromTheme(g_Icons[Icon::UserProperties]),
i18n("Rename the current selected payee"),
i18n("Use this to start renaming the selected payee."));
KGuiItem::assign(m_renameButton, renameButtonItem);
m_renameButton->setToolTip(renameButtonItem.toolTip());
KGuiItem deleteButtonItem(QString(),
QIcon::fromTheme(g_Icons[Icon::ListRemoveUser]),
i18n("Delete selected payee(s)"),
i18n("Use this to delete the selected payee. You can also select "
"multiple payees to be deleted."));
KGuiItem::assign(m_deleteButton, deleteButtonItem);
m_deleteButton->setToolTip(deleteButtonItem.toolTip());
KGuiItem mergeButtonItem(QString(),
QIcon::fromTheme(g_Icons[Icon::Merge]),
i18n("Merge multiple selected payees"),
i18n("Use this to merge multiple selected payees."));
KGuiItem::assign(m_mergeButton, mergeButtonItem);
m_mergeButton->setToolTip(mergeButtonItem.toolTip());
KGuiItem updateButtonItem(i18nc("Update payee", "Update"),
QIcon::fromTheme(g_Icons[Icon::DialogOK]),
i18n("Accepts the entered data and stores it"),
i18n("Use this to accept the modified data."));
KGuiItem::assign(m_updateButton, updateButtonItem);
KGuiItem syncButtonItem(i18nc("Sync payee", "Sync"),
QIcon::fromTheme(g_Icons[Icon::Refresh]),
i18n("Fetches the payee's data from your addressbook."),
i18n("Use this to fetch payee's data."));
KGuiItem::assign(m_syncAddressbook, syncButtonItem);
KGuiItem sendMailButtonItem(i18nc("Send mail", "Send"),
QIcon::fromTheme(g_Icons[Icon::MailMessage]),
i18n("Creates new e-mail to your payee."),
i18n("Use this to create new e-mail to your payee."));
KGuiItem::assign(m_sendMail, sendMailButtonItem);
m_updateButton->setEnabled(false);
m_syncAddressbook->setEnabled(false);
#ifndef KMM_ADDRESSBOOK_FOUND
m_syncAddressbook->hide();
#endif
matchTypeCombo->setCurrentIndex(0);
checkMatchIgnoreCase->setEnabled(false);
checkEnableDefaultCategory->setChecked(false);
labelDefaultCategory->setEnabled(false);
comboDefaultCategory->setEnabled(false);
QList<KMyMoneyRegister::Column> cols;
cols << KMyMoneyRegister::DateColumn;
cols << KMyMoneyRegister::AccountColumn;
cols << KMyMoneyRegister::DetailColumn;
cols << KMyMoneyRegister::ReconcileFlagColumn;
cols << KMyMoneyRegister::PaymentColumn;
cols << KMyMoneyRegister::DepositColumn;
m_register->setupRegister(MyMoneyAccount(), cols);
m_register->setSelectionMode(QTableWidget::SingleSelection);
m_register->setDetailsColumnType(KMyMoneyRegister::AccountFirst);
m_balanceLabel->hide();
connect(m_contact, SIGNAL(contactFetched(ContactData)), this, SLOT(slotContactFetched(ContactData)));
connect(m_payeesList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(slotSelectPayee(QListWidgetItem*,QListWidgetItem*)));
connect(m_payeesList, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectPayee()));
connect(m_payeesList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotStartRename(QListWidgetItem*)));
connect(m_payeesList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(slotRenamePayee(QListWidgetItem*)));
connect(m_payeesList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotOpenContextMenu(QPoint)));
connect(m_renameButton, SIGNAL(clicked()), this, SLOT(slotRenameButtonCliked()));
connect(m_deleteButton, SIGNAL(clicked()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::PayeeDelete]), SLOT(trigger()));
connect(m_mergeButton, SIGNAL(clicked()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::PayeeMerge]), SLOT(trigger()));
connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotPayeeNew()));
connect(addressEdit, SIGNAL(textChanged()), this, SLOT(slotPayeeDataChanged()));
connect(postcodeEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged()));
connect(telephoneEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged()));
connect(emailEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged()));
connect(notesEdit, SIGNAL(textChanged()), this, SLOT(slotPayeeDataChanged()));
connect(matchKeyEditList, SIGNAL(changed()), this, SLOT(slotKeyListChanged()));
connect(matchTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPayeeDataChanged()));
connect(checkMatchIgnoreCase, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged()));
connect(checkEnableDefaultCategory, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged()));
connect(comboDefaultCategory, SIGNAL(accountSelected(QString)), this, SLOT(slotPayeeDataChanged()));
connect(buttonSuggestACategory, SIGNAL(clicked()), this, SLOT(slotChooseDefaultAccount()));
connect(m_updateButton, SIGNAL(clicked()), this, SLOT(slotUpdatePayee()));
connect(m_syncAddressbook, SIGNAL(clicked()), this, SLOT(slotSyncAddressBook()));
connect(m_helpButton, SIGNAL(clicked()), this, SLOT(slotHelp()));
connect(m_sendMail, SIGNAL(clicked()), this, SLOT(slotSendMail()));
connect(m_register, SIGNAL(editTransaction()), this, SLOT(slotSelectTransaction()));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadPayees()));
connect(m_filterBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeFilter(int)));
connect(payeeIdentifiers, SIGNAL(dataChanged()), this, SLOT(slotPayeeDataChanged()));
// use the size settings of the last run (if any)
KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings");
m_splitter->restoreState(grp.readEntry("KPayeesViewSplitterSize", QByteArray()));
m_splitter->setChildrenCollapsible(false);
//At start we haven't any payee selected
m_tabWidget->setEnabled(false); // disable tab widget
m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons
m_renameButton->setEnabled(false);
m_mergeButton->setEnabled(false);
m_payee = MyMoneyPayee(); // make sure we don't access an undefined payee
clearItemData();
}
void KPayeesView::slotChooseDefaultAccount()
{
MyMoneyFile* file = MyMoneyFile::instance();
QMap<QString, int> account_count;
KMyMoneyRegister::RegisterItem* item = m_register->firstItem();
while (item) {
//only walk through selectable items. eg. transactions and not group markers
if (item->isSelectable()) {
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(item);
MyMoneySplit s = t->transaction().splitByPayee(m_payee.id());
const MyMoneyAccount& acc = file->account(s.accountId());
QString txt;
if (s.action() != MyMoneySplit::ActionAmortization
- && acc.accountType() != MyMoneyAccount::AssetLoan
+ && acc.accountType() != eMyMoney::Account::AssetLoan
&& !file->isTransfer(t->transaction())
&& t->transaction().splitCount() == 2) {
MyMoneySplit s0 = t->transaction().splitByAccount(s.accountId(), false);
if (account_count.contains(s0.accountId())) {
account_count[s0.accountId()]++;
} else {
account_count[s0.accountId()] = 1;
}
}
}
item = item->nextItem();
}
QMap<QString, int>::Iterator most_frequent, iter;
most_frequent = account_count.begin();
for (iter = account_count.begin(); iter != account_count.end(); ++iter) {
if (iter.value() > most_frequent.value()) {
most_frequent = iter;
}
}
if (most_frequent != account_count.end()) {
checkEnableDefaultCategory->setChecked(true);
comboDefaultCategory->setSelected(most_frequent.key());
setDirty();
}
}
void KPayeesView::slotStartRename(QListWidgetItem* item)
{
m_allowEditing = true;
m_payeesList->editItem(item);
}
void KPayeesView::slotRenameButtonCliked()
{
if (m_payeesList->currentItem() && m_payeesList->selectedItems().count() == 1) {
slotStartRename(m_payeesList->currentItem());
}
}
// This variant is only called when a single payee is selected and renamed.
void KPayeesView::slotRenamePayee(QListWidgetItem* p)
{
//if there is no current item selected, exit
if (m_allowEditing == false || !m_payeesList->currentItem() || p != m_payeesList->currentItem())
return;
//qDebug() << "[KPayeesView::slotRenamePayee]";
// create a copy of the new name without appended whitespaces
QString new_name = p->text();
if (m_payee.name() != new_name) {
MyMoneyFileTransaction ft;
try {
// check if we already have a payee with the new name
try {
// this function call will throw an exception, if the payee
// hasn't been found.
MyMoneyFile::instance()->payeeByName(new_name);
// the name already exists, ask the user whether he's sure to keep the name
if (KMessageBox::questionYesNo(this,
i18n("A payee with the name '%1' already exists. It is not advisable to have "
"multiple payees with the same identification name. Are you sure you would like "
"to rename the payee?", new_name)) != KMessageBox::Yes) {
p->setText(m_payee.name());
return;
}
} catch (const MyMoneyException &) {
// all ok, the name is unique
}
m_payee.setName(new_name);
m_newName = new_name;
MyMoneyFile::instance()->modifyPayee(m_payee);
// the above call to modifyPayee will reload the view so
// all references and pointers to the view have to be
// re-established.
// make sure, that the record is visible even if it moved
// out of sight due to the rename operation
ensurePayeeVisible(m_payee.id());
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to modify payee"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
}
} else {
p->setText(new_name);
}
}
void KPayeesView::ensurePayeeVisible(const QString& id)
{
for (int i = 0; i < m_payeesList->count(); ++i) {
KPayeeListItem* p = dynamic_cast<KPayeeListItem*>(m_payeesList->item(0));
if (p && p->payee().id() == id) {
m_payeesList->scrollToItem(p, QAbstractItemView::PositionAtCenter);
m_payeesList->setCurrentItem(p); // active item and deselect all others
m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it
break;
}
}
}
void KPayeesView::selectedPayees(QList<MyMoneyPayee>& payeesList) const
{
QList<QListWidgetItem *> selectedItems = m_payeesList->selectedItems();
QList<QListWidgetItem *>::ConstIterator itemsIt = selectedItems.constBegin();
while (itemsIt != selectedItems.constEnd()) {
KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(*itemsIt);
if (item)
payeesList << item->payee();
++itemsIt;
}
}
void KPayeesView::slotSelectPayee(QListWidgetItem* cur, QListWidgetItem* prev)
{
Q_UNUSED(cur);
Q_UNUSED(prev);
m_allowEditing = false;
}
void KPayeesView::slotSelectPayee()
{
// check if the content of a currently selected payee was modified
// and ask to store the data
if (isDirty()) {
QString question = QString("<qt>%1</qt>").arg(i18n("Do you want to save the changes for <b>%1</b>?", m_newName));
if (KMessageBox::questionYesNo(this, question, i18n("Save changes")) == KMessageBox::Yes) {
m_inSelection = true;
slotUpdatePayee();
m_inSelection = false;
}
}
// make sure we always clear the selected list when listing again
m_selectedPayeesList.clear();
// loop over all payees and count the number of payees, also
// obtain last selected payee
selectedPayees(m_selectedPayeesList);
emit selectObjects(m_selectedPayeesList);
if (m_selectedPayeesList.isEmpty()) {
m_tabWidget->setEnabled(false); // disable tab widget
m_balanceLabel->hide();
m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons
m_renameButton->setEnabled(false);
m_mergeButton->setEnabled(false);
clearItemData();
m_payee = MyMoneyPayee();
m_syncAddressbook->setEnabled(false);
return; // make sure we don't access an undefined payee
}
m_deleteButton->setEnabled(true); //re-enable delete button
m_syncAddressbook->setEnabled(true);
// if we have multiple payees selected, clear and disable the payee information
if (m_selectedPayeesList.count() > 1) {
m_tabWidget->setEnabled(false); // disable tab widget
m_renameButton->setEnabled(false); // disable also the rename button
m_mergeButton->setEnabled(true);
m_balanceLabel->hide();
clearItemData();
} else {
m_mergeButton->setEnabled(false);
m_renameButton->setEnabled(true);
}
// otherwise we have just one selected, enable payee information widget
m_tabWidget->setEnabled(true);
m_balanceLabel->show();
// as of now we are updating only the last selected payee, and until
// selection mode of the QListView has been changed to Extended, this
// will also be the only selection and behave exactly as before - Andreas
try {
m_payee = m_selectedPayeesList[0];
m_newName = m_payee.name();
addressEdit->setEnabled(true);
addressEdit->setText(m_payee.address());
postcodeEdit->setEnabled(true);
postcodeEdit->setText(m_payee.postcode());
telephoneEdit->setEnabled(true);
telephoneEdit->setText(m_payee.telephone());
emailEdit->setEnabled(true);
emailEdit->setText(m_payee.email());
notesEdit->setText(m_payee.notes());
QStringList keys;
bool ignorecase = false;
MyMoneyPayee::payeeMatchType type = m_payee.matchData(ignorecase, keys);
matchTypeCombo->setCurrentIndex(matchTypeCombo->findData(type));
matchKeyEditList->clear();
matchKeyEditList->insertStringList(keys);
checkMatchIgnoreCase->setChecked(ignorecase);
checkEnableDefaultCategory->setChecked(m_payee.defaultAccountEnabled());
comboDefaultCategory->setSelected(m_payee.defaultAccountId());
payeeIdentifiers->setSource(m_payee);
slotPayeeDataChanged();
showTransactions();
} catch (const MyMoneyException &e) {
qDebug("exception during display of payee: %s at %s:%ld", qPrintable(e.what()), qPrintable(e.file()), e.line());
m_register->clear();
m_selectedPayeesList.clear();
m_payee = MyMoneyPayee();
}
m_allowEditing = true;
}
void KPayeesView::clearItemData()
{
addressEdit->setText(QString());
postcodeEdit->setText(QString());
telephoneEdit->setText(QString());
emailEdit->setText(QString());
notesEdit->setText(QString());
showTransactions();
}
void KPayeesView::showTransactions()
{
MyMoneyMoney balance;
MyMoneyFile *file = MyMoneyFile::instance();
MyMoneySecurity base = file->baseCurrency();
// setup sort order
m_register->setSortOrder(KMyMoneyGlobalSettings::sortSearchView());
// clear the register
m_register->clear();
if (m_selectedPayeesList.isEmpty() || !m_tabWidget->isEnabled()) {
m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction())));
return;
}
// setup the list and the pointer vector
MyMoneyTransactionFilter filter;
for (QList<MyMoneyPayee>::const_iterator it = m_selectedPayeesList.constBegin();
it != m_selectedPayeesList.constEnd();
++it)
filter.addPayee((*it).id());
filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate());
// retrieve the list from the engine
file->transactionList(m_transactionList, filter);
// create the elements for the register
QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
QMap<QString, int> uniqueMap;
MyMoneyMoney deposit, payment;
int splitCount = 0;
bool balanceAccurate = true;
for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) {
const MyMoneySplit& split = (*it).second;
MyMoneyAccount acc = file->account(split.accountId());
++splitCount;
uniqueMap[(*it).first.id()]++;
KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]);
// take care of foreign currencies
MyMoneyMoney val = split.shares().abs();
if (acc.currencyId() != base.id()) {
const MyMoneyPrice &price = file->price(acc.currencyId(), base.id());
// in case the price is valid, we use it. Otherwise, we keep
// a flag that tells us that the balance is somewhat inaccurate
if (price.isValid()) {
val *= price.rate(base.id());
} else {
balanceAccurate = false;
}
}
if (split.shares().isNegative()) {
payment += val;
} else {
deposit += val;
}
}
balance = deposit - payment;
// add the group markers
m_register->addGroupMarkers();
// sort the transactions according to the sort setting
m_register->sortItems();
// remove trailing and adjacent markers
m_register->removeUnwantedGroupMarkers();
m_register->updateRegister(true);
// we might end up here with updates disabled on the register so
// make sure that we enable updates here
m_register->setUpdatesEnabled(true);
m_balanceLabel->setText(i18n("Balance: %1%2",
balanceAccurate ? "" : "~",
balance.formatMoney(file->baseCurrency().smallestAccountFraction())));
}
void KPayeesView::slotKeyListChanged()
{
bool rc = false;
bool ignorecase = false;
QStringList keys;
m_payee.matchData(ignorecase, keys);
if (matchTypeCombo->currentData().toUInt() == MyMoneyPayee::matchKey) {
rc |= (keys != matchKeyEditList->items());
}
setDirty(rc);
}
void KPayeesView::slotPayeeDataChanged()
{
bool rc = false;
if (m_tabWidget->isEnabled()) {
rc |= ((m_payee.email().isEmpty() != emailEdit->text().isEmpty())
|| (!emailEdit->text().isEmpty() && m_payee.email() != emailEdit->text()));
rc |= ((m_payee.address().isEmpty() != addressEdit->toPlainText().isEmpty())
|| (!addressEdit->toPlainText().isEmpty() && m_payee.address() != addressEdit->toPlainText()));
rc |= ((m_payee.postcode().isEmpty() != postcodeEdit->text().isEmpty())
|| (!postcodeEdit->text().isEmpty() && m_payee.postcode() != postcodeEdit->text()));
rc |= ((m_payee.telephone().isEmpty() != telephoneEdit->text().isEmpty())
|| (!telephoneEdit->text().isEmpty() && m_payee.telephone() != telephoneEdit->text()));
rc |= ((m_payee.name().isEmpty() != m_newName.isEmpty())
|| (!m_newName.isEmpty() && m_payee.name() != m_newName));
rc |= ((m_payee.notes().isEmpty() != notesEdit->toPlainText().isEmpty())
|| (!notesEdit->toPlainText().isEmpty() && m_payee.notes() != notesEdit->toPlainText()));
bool ignorecase = false;
QStringList keys;
MyMoneyPayee::payeeMatchType type = m_payee.matchData(ignorecase, keys);
rc |= (static_cast<unsigned int>(type) != matchTypeCombo->currentData().toUInt());
checkMatchIgnoreCase->setEnabled(false);
matchKeyEditList->setEnabled(false);
if (matchTypeCombo->currentData().toUInt() != MyMoneyPayee::matchDisabled) {
checkMatchIgnoreCase->setEnabled(true);
// if we turn matching on, we default to 'ignore case'
// TODO maybe make the default a user option
if (type == MyMoneyPayee::matchDisabled && matchTypeCombo->currentData().toUInt() != MyMoneyPayee::matchDisabled)
checkMatchIgnoreCase->setChecked(true);
rc |= (ignorecase != checkMatchIgnoreCase->isChecked());
if (matchTypeCombo->currentData().toUInt() == MyMoneyPayee::matchKey) {
matchKeyEditList->setEnabled(true);
rc |= (keys != matchKeyEditList->items());
}
}
rc |= (checkEnableDefaultCategory->isChecked() != m_payee.defaultAccountEnabled());
if (checkEnableDefaultCategory->isChecked()) {
comboDefaultCategory->setEnabled(true);
labelDefaultCategory->setEnabled(true);
// this is only going to understand the first in the list of selected accounts
if (comboDefaultCategory->getSelected().isEmpty()) {
rc |= !m_payee.defaultAccountId().isEmpty();
} else {
QString temp = comboDefaultCategory->getSelected();
rc |= (temp.isEmpty() != m_payee.defaultAccountId().isEmpty())
|| (!m_payee.defaultAccountId().isEmpty() && temp != m_payee.defaultAccountId());
}
} else {
comboDefaultCategory->setEnabled(false);
labelDefaultCategory->setEnabled(false);
}
rc |= (m_payee.payeeIdentifiers() != payeeIdentifiers->identifiers());
}
setDirty(rc);
}
void KPayeesView::slotUpdatePayee()
{
if (isDirty()) {
MyMoneyFileTransaction ft;
setDirty(false);
try {
m_payee.setName(m_newName);
m_payee.setAddress(addressEdit->toPlainText());
m_payee.setPostcode(postcodeEdit->text());
m_payee.setTelephone(telephoneEdit->text());
m_payee.setEmail(emailEdit->text());
m_payee.setNotes(notesEdit->toPlainText());
m_payee.setMatchData(static_cast<MyMoneyPayee::payeeMatchType>(matchTypeCombo->currentData().toUInt()), checkMatchIgnoreCase->isChecked(), matchKeyEditList->items());
m_payee.setDefaultAccountId();
m_payee.resetPayeeIdentifiers(payeeIdentifiers->identifiers());
if (checkEnableDefaultCategory->isChecked()) {
QString temp;
if (!comboDefaultCategory->getSelected().isEmpty()) {
temp = comboDefaultCategory->getSelected();
m_payee.setDefaultAccountId(temp);
}
}
MyMoneyFile::instance()->modifyPayee(m_payee);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to modify payee"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
}
void KPayeesView::slotSyncAddressBook()
{
if (m_payeeRows.isEmpty()) { // empty list means no syncing is pending...
foreach (auto item, m_payeesList->selectedItems()) {
m_payeeRows.append(m_payeesList->row(item)); // ...so initialize one
}
m_payeesList->clearSelection(); // otherwise slotSelectPayee will be run after every payee update
// m_syncAddressbook->setEnabled(false); // disallow concurent syncs
}
if (m_payeeRows.count() <= m_payeeRow) {
KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(m_payeesList->currentItem());
if (item) { // update ui if something is selected
m_payee = item->payee();
addressEdit->setText(m_payee.address());
postcodeEdit->setText(m_payee.postcode());
telephoneEdit->setText(m_payee.telephone());
}
m_payeeRows.clear(); // that means end of sync
m_payeeRow = 0;
return;
}
KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(m_payeesList->item(m_payeeRows.at(m_payeeRow)));
if (item)
m_payee = item->payee();
++m_payeeRow;
m_contact->fetchContact(m_payee.email()); // search for payee's data in addressbook and receive it in slotContactFetched
}
void KPayeesView::slotContactFetched(const ContactData &identity)
{
if (!identity.email.isEmpty()) { // empty e-mail means no identity fetched
QString txt;
if (!identity.street.isEmpty())
txt.append(identity.street + "\n");
if (!identity.locality.isEmpty()) {
txt.append(identity.locality);
if (!identity.postalCode.isEmpty())
txt.append(' ' + identity.postalCode + "\n");
else
txt.append("\n");
}
if (!identity.country.isEmpty())
txt.append(identity.country + "\n");
if (!txt.isEmpty() && m_payee.address().compare(txt) != 0)
m_payee.setAddress(txt);
if (!identity.postalCode.isEmpty() && m_payee.postcode().compare(identity.postalCode) != 0)
m_payee.setPostcode(identity.postalCode);
if (!identity.phoneNumber.isEmpty() && m_payee.telephone().compare(identity.phoneNumber) != 0)
m_payee.setTelephone(identity.phoneNumber);
MyMoneyFileTransaction ft;
try {
MyMoneyFile::instance()->modifyPayee(m_payee);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to modify payee"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
slotSyncAddressBook(); // process next payee
}
void KPayeesView::slotSendMail()
{
QRegularExpression re(".+@.+");
if (re.match(m_payee.email()).hasMatch())
QDesktopServices::openUrl(QUrl(QStringLiteral("mailto:?to=") + m_payee.email(), QUrl::TolerantMode));
}
void KPayeesView::showEvent(QShowEvent* event)
{
if (m_needLoad)
init();
emit aboutToShow();
if (m_needReload) {
loadPayees();
m_needReload = false;
}
// don't forget base class implementation
QWidget::showEvent(event);
QList<MyMoneyPayee> list;
selectedPayees(list);
emit selectObjects(list);
}
void KPayeesView::slotLoadPayees()
{
if (isVisible()) {
if (m_inSelection)
QTimer::singleShot(0, this, SLOT(slotLoadPayees()));
else
loadPayees();
} else {
m_needReload = true;
}
}
void KPayeesView::loadPayees()
{
if (m_inSelection)
return;
QMap<QString, bool> isSelected;
QString id;
MyMoneyFile* file = MyMoneyFile::instance();
// remember which items are selected in the list
QList<QListWidgetItem *> selectedItems = m_payeesList->selectedItems();
QList<QListWidgetItem *>::const_iterator payeesIt = selectedItems.constBegin();
while (payeesIt != selectedItems.constEnd()) {
KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(*payeesIt);
if (item)
isSelected[item->payee().id()] = true;
++payeesIt;
}
// keep current selected item
KPayeeListItem *currentItem = static_cast<KPayeeListItem *>(m_payeesList->currentItem());
if (currentItem)
id = currentItem->payee().id();
m_allowEditing = false;
// clear the list
m_searchWidget->clear();
m_searchWidget->updateSearch();
m_payeesList->clear();
m_register->clear();
currentItem = 0;
QList<MyMoneyPayee>list = file->payeeList();
QList<MyMoneyPayee>::ConstIterator it;
for (it = list.constBegin(); it != list.constEnd(); ++it) {
if (m_payeeFilterType == eAllPayees ||
(m_payeeFilterType == eReferencedPayees && file->isReferenced(*it)) ||
(m_payeeFilterType == eUnusedPayees && !file->isReferenced(*it))) {
KPayeeListItem* item = new KPayeeListItem(m_payeesList, *it);
if (item->payee().id() == id)
currentItem = item;
if (isSelected[item->payee().id()])
item->setSelected(true);
}
}
m_payeesList->sortItems();
if (currentItem) {
m_payeesList->setCurrentItem(currentItem);
m_payeesList->scrollToItem(currentItem);
}
m_filterProxyModel->invalidate();
comboDefaultCategory->expandAll();
slotSelectPayee(0, 0);
m_allowEditing = true;
}
void KPayeesView::slotSelectTransaction()
{
QList<KMyMoneyRegister::RegisterItem*> list = m_register->selectedItems();
if (!list.isEmpty()) {
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(list[0]);
if (t)
emit transactionSelected(t->split().accountId(), t->transaction().id());
}
}
void KPayeesView::slotSelectPayeeAndTransaction(const QString& payeeId, const QString& accountId, const QString& transactionId)
{
if (!isVisible())
return;
try {
// clear filter
m_searchWidget->clear();
m_searchWidget->updateSearch();
// deselect all other selected items
QList<QListWidgetItem *> selectedItems = m_payeesList->selectedItems();
QList<QListWidgetItem *>::const_iterator payeesIt = selectedItems.constBegin();
while (payeesIt != selectedItems.constEnd()) {
KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(*payeesIt);
if (item)
item->setSelected(false);
++payeesIt;
}
// find the payee in the list
QListWidgetItem* it;
for (int i = 0; i < m_payeesList->count(); ++i) {
it = m_payeesList->item(i);
KPayeeListItem* item = dynamic_cast<KPayeeListItem *>(it);
if (item && item->payee().id() == payeeId) {
m_payeesList->scrollToItem(it, QAbstractItemView::PositionAtCenter);
m_payeesList->setCurrentItem(it); // active item and deselect all others
m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it
//make sure the payee selection is updated and transactions are updated accordingly
slotSelectPayee();
KMyMoneyRegister::RegisterItem *item = 0;
for (int i = 0; i < m_register->rowCount(); ++i) {
item = m_register->itemAtRow(i);
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(item);
if (t) {
if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) {
m_register->selectItem(item);
m_register->ensureItemVisible(item);
break;
}
}
}
// quit out of for() loop
break;
}
}
} catch (const MyMoneyException &e) {
qWarning("Unexpected exception in KPayeesView::slotSelectPayeeAndTransaction %s", qPrintable(e.what()));
}
}
void KPayeesView::slotOpenContextMenu(const QPoint& /*p*/)
{
KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(m_payeesList->currentItem());
if (item) {
slotSelectPayee();
emit openContextMenu(item->payee());
}
}
void KPayeesView::slotPayeeNew()
{
kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::PayeeNew])->trigger();
}
void KPayeesView::slotHelp()
{
KHelpClient::invokeHelp("details.payees");
}
void KPayeesView::slotChangeFilter(int index)
{
//update the filter type then reload the payees list
m_payeeFilterType = index;
loadPayees();
}
bool KPayeesView::isDirty() const
{
return m_updateButton->isEnabled();
}
void KPayeesView::setDirty(bool dirty)
{
m_updateButton->setEnabled(dirty);
}
diff --git a/kmymoney/views/kscheduledview.cpp b/kmymoney/views/kscheduledview.cpp
index 2592bd634..a8effa180 100644
--- a/kmymoney/views/kscheduledview.cpp
+++ b/kmymoney/views/kscheduledview.cpp
@@ -1,611 +1,612 @@
/***************************************************************************
kscheduledview.cpp - description
-------------------
begin : Sun Jan 27 2002
copyright : (C) 2000-2002 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "kscheduledview.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
#include <QTimer>
#include <QPushButton>
#include <QMenu>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KConfig>
#include <KMessageBox>
#include <KSharedConfig>
#include <KActionCollection>
#include <KTreeWidgetSearchLine>
#include <KTreeWidgetSearchLineWidget>
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneyutils.h"
#include "kmymoneyglobalsettings.h"
#include "kscheduletreeitem.h"
#include "ktreewidgetfilterlinewidget.h"
#include "kmymoney.h"
#include "icons/icons.h"
#include "mymoneyutils.h"
#include "mymoneyaccount.h"
#include "mymoneyschedule.h"
#include "mymoneyfile.h"
+#include "mymoneypayee.h"
using namespace Icons;
KScheduledView::KScheduledView(QWidget *parent) :
QWidget(parent),
m_openBills(true),
m_openDeposits(true),
m_openTransfers(true),
m_openLoans(true),
m_needLoad(true)
{
}
KScheduledView::~KScheduledView()
{
if(!m_needLoad)
writeConfig();
}
void KScheduledView::setDefaultFocus()
{
QTimer::singleShot(0, m_searchWidget->searchLine(), SLOT(setFocus()));
}
void KScheduledView::init()
{
m_needLoad = false;
setupUi(this);
// create the searchline widget
// and insert it into the existing layout
m_searchWidget = new KTreeWidgetFilterLineWidget(this, m_scheduleTree);
vboxLayout->insertWidget(1, m_searchWidget);
//enable custom context menu
m_scheduleTree->setContextMenuPolicy(Qt::CustomContextMenu);
m_scheduleTree->setSelectionMode(QAbstractItemView::SingleSelection);
readConfig();
connect(m_qbuttonNew, SIGNAL(clicked()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::ScheduleNew]), SLOT(trigger()));
// attach popup to 'Filter...' button
m_kaccPopup = new QMenu(this);
m_accountsCombo->setMenu(m_kaccPopup);
connect(m_kaccPopup, SIGNAL(triggered(QAction*)), this, SLOT(slotAccountActivated()));
KGuiItem::assign(m_qbuttonNew, KMyMoneyUtils::scheduleNewGuiItem());
KGuiItem::assign(m_accountsCombo, KMyMoneyUtils::accountsFilterGuiItem());
connect(m_scheduleTree, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotListViewContextMenu(QPoint)));
connect(m_scheduleTree, SIGNAL(itemSelectionChanged()),
this, SLOT(slotSetSelectedItem()));
connect(m_scheduleTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),
this, SLOT(slotListItemExecuted(QTreeWidgetItem*,int)));
connect(m_scheduleTree, SIGNAL(itemExpanded(QTreeWidgetItem*)),
this, SLOT(slotListViewExpanded(QTreeWidgetItem*)));
connect(m_scheduleTree, SIGNAL(itemCollapsed(QTreeWidgetItem*)),
this, SLOT(slotListViewCollapsed(QTreeWidgetItem*)));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotReloadView()));
}
static bool accountNameLessThan(const MyMoneyAccount& acc1, const MyMoneyAccount& acc2)
{
return acc1.name().toLower() < acc2.name().toLower();
}
void KScheduledView::refresh(bool full, const QString& schedId)
{
m_scheduleTree->header()->setFont(KMyMoneyGlobalSettings::listHeaderFont());
m_scheduleTree->clear();
try {
if (full) {
try {
m_kaccPopup->clear();
MyMoneyFile* file = MyMoneyFile::instance();
// extract a list of all accounts under the asset group
// and sort them by name
QList<MyMoneyAccount> list;
QStringList accountList = file->asset().accountList();
accountList.append(file->liability().accountList());
file->accountList(list, accountList, true);
qStableSort(list.begin(), list.end(), accountNameLessThan);
QList<MyMoneyAccount>::ConstIterator it_a;
for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
if (!(*it_a).isClosed()) {
QAction* act;
act = m_kaccPopup->addAction((*it_a).name());
act->setCheckable(true);
act->setChecked(true);
}
}
} catch (const MyMoneyException &e) {
KMessageBox::detailedError(this, i18n("Unable to load accounts: "), e.what());
}
}
MyMoneyFile *file = MyMoneyFile::instance();
QList<MyMoneySchedule> scheduledItems = file->scheduleList();
if (scheduledItems.count() == 0)
return;
//disable sorting for performance
m_scheduleTree->setSortingEnabled(false);
KScheduleTreeItem *itemBills = new KScheduleTreeItem(m_scheduleTree);
itemBills->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewExpense]));
itemBills->setText(0, i18n("Bills"));
itemBills->setData(0, KScheduleTreeItem::OrderRole, QVariant("0"));
itemBills->setFirstColumnSpanned(true);
itemBills->setFlags(Qt::ItemIsEnabled);
QFont bold = itemBills->font(0);
bold.setBold(true);
itemBills->setFont(0, bold);
KScheduleTreeItem *itemDeposits = new KScheduleTreeItem(m_scheduleTree);
itemDeposits->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewIncome]));
itemDeposits->setText(0, i18n("Deposits"));
itemDeposits->setData(0, KScheduleTreeItem::OrderRole, QVariant("1"));
itemDeposits->setFirstColumnSpanned(true);
itemDeposits->setFlags(Qt::ItemIsEnabled);
itemDeposits->setFont(0, bold);
KScheduleTreeItem *itemLoans = new KScheduleTreeItem(m_scheduleTree);
itemLoans->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewLoan]));
itemLoans->setText(0, i18n("Loans"));
itemLoans->setData(0, KScheduleTreeItem::OrderRole, QVariant("2"));
itemLoans->setFirstColumnSpanned(true);
itemLoans->setFlags(Qt::ItemIsEnabled);
itemLoans->setFont(0, bold);
KScheduleTreeItem *itemTransfers = new KScheduleTreeItem(m_scheduleTree);
itemTransfers->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewFinancialTransfer]));
itemTransfers->setText(0, i18n("Transfers"));
itemTransfers->setData(0, KScheduleTreeItem::OrderRole, QVariant("3"));
itemTransfers->setFirstColumnSpanned(true);
itemTransfers->setFlags(Qt::ItemIsEnabled);
itemTransfers->setFont(0, bold);
QList<MyMoneySchedule>::Iterator it;
QTreeWidgetItem *openItem = 0;
for (it = scheduledItems.begin(); it != scheduledItems.end(); ++it) {
MyMoneySchedule schedData = (*it);
QTreeWidgetItem* item = 0;
bool bContinue = true;
QStringList::iterator accIt;
for (accIt = m_filterAccounts.begin(); accIt != m_filterAccounts.end(); ++accIt) {
if (*accIt == schedData.account().id()) {
bContinue = false; // Filter it out
break;
}
}
if (!bContinue)
continue;
QTreeWidgetItem* parent = 0;
switch (schedData.type()) {
- case MyMoneySchedule::TYPE_ANY:
+ case eMyMoney::Schedule::Type::Any:
// Should we display an error ?
// We just sort it as bill and fall through here
- case MyMoneySchedule::TYPE_BILL:
+ case eMyMoney::Schedule::Type::Bill:
parent = itemBills;
break;
- case MyMoneySchedule::TYPE_DEPOSIT:
+ case eMyMoney::Schedule::Type::Deposit:
parent = itemDeposits;
break;
- case MyMoneySchedule::TYPE_TRANSFER:
+ case eMyMoney::Schedule::Type::Transfer:
parent = itemTransfers;
break;
- case MyMoneySchedule::TYPE_LOANPAYMENT:
+ case eMyMoney::Schedule::Type::LoanPayment:
parent = itemLoans;
break;
}
if (parent) {
if (!KMyMoneyGlobalSettings::hideFinishedSchedules() || !schedData.isFinished()) {
item = addScheduleItem(parent, schedData);
if (schedData.id() == schedId)
openItem = item;
}
}
}
if (openItem) {
m_scheduleTree->setCurrentItem(openItem);
}
// using a timeout is the only way, I got the 'ensureTransactionVisible'
// working when coming from hidden form to visible form. I assume, this
// has something to do with the delayed update of the display somehow.
resize(width(), height() - 1);
QTimer::singleShot(10, this, SLOT(slotTimerDone()));
m_scheduleTree->update();
// force repaint in case the filter is set
m_searchWidget->searchLine()->updateSearch(QString());
if (m_openBills)
itemBills->setExpanded(true);
if (m_openDeposits)
itemDeposits->setExpanded(true);
if (m_openTransfers)
itemTransfers->setExpanded(true);
if (m_openLoans)
itemLoans->setExpanded(true);
} catch (const MyMoneyException &e) {
KMessageBox::error(this, e.what());
}
for (int i = 0; i < m_scheduleTree->columnCount(); ++i) {
m_scheduleTree->resizeColumnToContents(i);
}
//reenable sorting after loading items
m_scheduleTree->setSortingEnabled(true);
}
QTreeWidgetItem* KScheduledView::addScheduleItem(QTreeWidgetItem* parent, MyMoneySchedule& schedule)
{
KScheduleTreeItem* item = new KScheduleTreeItem(parent);
item->setData(0, Qt::UserRole, QVariant::fromValue(schedule));
item->setData(0, KScheduleTreeItem::OrderRole, schedule.name());
if (!schedule.isFinished()) {
if (schedule.isOverdue()) {
item->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewUpcominEvents]));
QBrush brush = item->foreground(0);
brush.setColor(Qt::red);
for (int i = 0; i < m_scheduleTree->columnCount(); ++i) {
item->setForeground(i, brush);
}
} else {
item->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewCalendarDay]));
}
} else {
item->setIcon(0, QIcon::fromTheme(g_Icons[Icon::DialogClose]));
QBrush brush = item->foreground(0);
brush.setColor(Qt::darkGreen);
for (int i = 0; i < m_scheduleTree->columnCount(); ++i) {
item->setForeground(i, brush);
}
}
try {
MyMoneyTransaction transaction = schedule.transaction();
MyMoneySplit s1 = (transaction.splits().size() < 1) ? MyMoneySplit() : transaction.splits()[0];
MyMoneySplit s2 = (transaction.splits().size() < 2) ? MyMoneySplit() : transaction.splits()[1];
QList<MyMoneySplit>::ConstIterator it_s;
MyMoneySplit split;
MyMoneyAccount acc;
switch (schedule.type()) {
- case MyMoneySchedule::TYPE_DEPOSIT:
+ case eMyMoney::Schedule::Type::Deposit:
if (s1.value().isNegative())
split = s2;
else
split = s1;
break;
- case MyMoneySchedule::TYPE_LOANPAYMENT:
+ case eMyMoney::Schedule::Type::LoanPayment:
for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) {
acc = MyMoneyFile::instance()->account((*it_s).accountId());
- if (acc.accountGroup() == MyMoneyAccount::Asset
- || acc.accountGroup() == MyMoneyAccount::Liability) {
- if (acc.accountType() != MyMoneyAccount::Loan
- && acc.accountType() != MyMoneyAccount::AssetLoan) {
+ if (acc.accountGroup() == eMyMoney::Account::Asset
+ || acc.accountGroup() == eMyMoney::Account::Liability) {
+ if (acc.accountType() != eMyMoney::Account::Loan
+ && acc.accountType() != eMyMoney::Account::AssetLoan) {
split = *it_s;
break;
}
}
}
if (it_s == transaction.splits().constEnd()) {
qWarning("Split for payment account not found in %s:%d.", __FILE__, __LINE__);
}
break;
default:
if (!s1.value().isPositive())
split = s1;
else
split = s2;
break;
}
acc = MyMoneyFile::instance()->account(split.accountId());
item->setText(0, schedule.name());
MyMoneySecurity currency = MyMoneyFile::instance()->currency(acc.currencyId());
QString accName = acc.name();
if (!accName.isEmpty()) {
item->setText(1, accName);
} else {
item->setText(1, "---");
}
item->setData(1, KScheduleTreeItem::OrderRole, QVariant(accName));
QString payeeName;
if (!s1.payeeId().isEmpty()) {
payeeName = MyMoneyFile::instance()->payee(s1.payeeId()).name();
item->setText(2, payeeName);
} else {
item->setText(2, "---");
}
item->setData(2, KScheduleTreeItem::OrderRole, QVariant(payeeName));
MyMoneyMoney amount = split.shares().abs();
item->setData(3, Qt::UserRole, QVariant::fromValue(amount));
if (!accName.isEmpty()) {
item->setText(3, QString("%1 ").arg(MyMoneyUtils::formatMoney(amount, acc, currency)));
} else {
//there are some cases where the schedule does not have an account
//in those cases the account will not have a fraction
//use base currency instead
item->setText(3, QString("%1 ").arg(MyMoneyUtils::formatMoney(amount, MyMoneyFile::instance()->baseCurrency())));
}
item->setTextAlignment(3, Qt::AlignRight | Qt::AlignVCenter);
item->setData(3, KScheduleTreeItem::OrderRole, QVariant::fromValue(amount));
// Do the real next payment like ms-money etc
QDate nextDueDate;
if (schedule.isFinished()) {
item->setText(4, i18nc("Finished schedule", "Finished"));
} else {
nextDueDate = schedule.adjustedNextDueDate();
item->setText(4, QLocale().toString(schedule.adjustedNextDueDate(), QLocale::ShortFormat));
}
item->setData(4, KScheduleTreeItem::OrderRole, QVariant(nextDueDate));
item->setText(5, i18nc("Frequency of schedule", schedule.occurrenceToString().toLatin1()));
item->setText(6, KMyMoneyUtils::paymentMethodToString(schedule.paymentType()));
} catch (const MyMoneyException &e) {
item->setText(0, "Error:");
item->setText(1, e.what());
}
return item;
}
void KScheduledView::slotTimerDone()
{
QTreeWidgetItem* item;
item = m_scheduleTree->currentItem();
if (item) {
m_scheduleTree->scrollToItem(item);
}
// force a repaint of all items to update the branches
/*for (item = m_scheduleTree->item(0); item != 0; item = m_scheduleTree->item(m_scheduleTree->row(item) + 1)) {
m_scheduleTree->repaintItem(item);
}
resize(width(), height() + 1);*/
}
void KScheduledView::slotReloadView()
{
m_needReload = true;
if (isVisible()) {
m_qbuttonNew->setEnabled(true);
refresh(true, m_selectedSchedule);
m_needReload = false;
QTimer::singleShot(50, this, SLOT(slotRearrange()));
}
}
void KScheduledView::showEvent(QShowEvent* event)
{
if (m_needLoad)
init();
emit aboutToShow();
if (m_needReload)
slotReloadView();
QWidget::showEvent(event);
}
void KScheduledView::slotRearrange()
{
resizeEvent(0);
}
void KScheduledView::readConfig()
{
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup grp = config->group("Last Use Settings");
m_openBills = grp.readEntry("KScheduleView_openBills", true);
m_openDeposits = grp.readEntry("KScheduleView_openDeposits", true);
m_openTransfers = grp.readEntry("KScheduleView_openTransfers", true);
m_openLoans = grp.readEntry("KScheduleView_openLoans", true);
QByteArray columns;
columns = grp.readEntry("KScheduleView_treeState", columns);
m_scheduleTree->header()->restoreState(columns);
m_scheduleTree->header()->setFont(KMyMoneyGlobalSettings::listHeaderFont());
}
void KScheduledView::writeConfig()
{
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup grp = config->group("Last Use Settings");
grp.writeEntry("KScheduleView_openBills", m_openBills);
grp.writeEntry("KScheduleView_openDeposits", m_openDeposits);
grp.writeEntry("KScheduleView_openTransfers", m_openTransfers);
grp.writeEntry("KScheduleView_openLoans", m_openLoans);
QByteArray columns = m_scheduleTree->header()->saveState();
grp.writeEntry("KScheduleView_treeState", columns);
config->sync();
}
void KScheduledView::slotListViewContextMenu(const QPoint& pos)
{
QTreeWidgetItem* item = m_scheduleTree->itemAt(pos);
if (item) {
try {
MyMoneySchedule schedule = item->data(0, Qt::UserRole).value<MyMoneySchedule>();
emit scheduleSelected(schedule);
m_selectedSchedule = schedule.id();
emit openContextMenu();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Error activating context menu"), e.what());
}
} else {
emit openContextMenu();
}
}
void KScheduledView::slotListItemExecuted(QTreeWidgetItem* item, int)
{
if (!item)
return;
try {
MyMoneySchedule schedule = item->data(0, Qt::UserRole).value<MyMoneySchedule>();
m_selectedSchedule = schedule.id();
emit editSchedule();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(this, i18n("Error executing item"), e.what());
}
}
void KScheduledView::slotAccountActivated()
{
m_filterAccounts.clear();
try {
int accountCount = 0;
MyMoneyFile* file = MyMoneyFile::instance();
// extract a list of all accounts under the asset and liability groups
// and sort them by name
QList<MyMoneyAccount> list;
QStringList accountList = file->asset().accountList();
accountList.append(file->liability().accountList());
file->accountList(list, accountList, true);
qStableSort(list.begin(), list.end(), accountNameLessThan);
QList<MyMoneyAccount>::ConstIterator it_a;
for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
if (!(*it_a).isClosed()) {
if (!m_kaccPopup->actions().value(accountCount)->isChecked()) {
m_filterAccounts.append((*it_a).id());
}
++accountCount;
}
}
refresh(false, m_selectedSchedule);
} catch (const MyMoneyException &e) {
KMessageBox::detailedError(this, i18n("Unable to filter account"), e.what());
}
}
void KScheduledView::slotListViewExpanded(QTreeWidgetItem* item)
{
if (item) {
if (item->text(0) == i18n("Bills"))
m_openBills = true;
else if (item->text(0) == i18n("Deposits"))
m_openDeposits = true;
else if (item->text(0) == i18n("Transfers"))
m_openTransfers = true;
else if (item->text(0) == i18n("Loans"))
m_openLoans = true;
}
}
void KScheduledView::slotListViewCollapsed(QTreeWidgetItem* item)
{
if (item) {
if (item->text(0) == i18n("Bills"))
m_openBills = false;
else if (item->text(0) == i18n("Deposits"))
m_openDeposits = false;
else if (item->text(0) == i18n("Transfers"))
m_openTransfers = false;
else if (item->text(0) == i18n("Loans"))
m_openLoans = false;
}
}
void KScheduledView::slotSelectSchedule(const QString& schedule)
{
refresh(true, schedule);
}
void KScheduledView::slotBriefEnterClicked(const MyMoneySchedule& schedule, const QDate& date)
{
Q_UNUSED(date);
emit scheduleSelected(schedule);
emit enterSchedule();
}
void KScheduledView::slotBriefSkipClicked(const MyMoneySchedule& schedule, const QDate& date)
{
Q_UNUSED(date);
emit scheduleSelected(schedule);
emit skipSchedule();
}
void KScheduledView::slotSetSelectedItem()
{
emit scheduleSelected(MyMoneySchedule());
QTreeWidgetItem* item = m_scheduleTree->currentItem();
if (item) {
try {
MyMoneySchedule schedule = item->data(0, Qt::UserRole).value<MyMoneySchedule>();
emit scheduleSelected(schedule);
m_selectedSchedule = schedule.id();
} catch (const MyMoneyException &e) {
qDebug("KScheduledView::slotSetSelectedItem: %s", qPrintable(e.what()));
}
}
}
diff --git a/kmymoney/views/ktagsview.cpp b/kmymoney/views/ktagsview.cpp
index 3a401606a..ec88e6881 100644
--- a/kmymoney/views/ktagsview.cpp
+++ b/kmymoney/views/ktagsview.cpp
@@ -1,683 +1,685 @@
/***************************************************************************
ktagsview.h
-------------
begin : Sat Oct 13 2012
copyright : (C) 2012 by Alessandro Russo <axela74@yahoo.it>
***************************************************************************/
/***************************************************************************
* *
* 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 "ktagsview.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QPushButton>
#include <QComboBox>
#include <QLabel>
#include <QTabWidget>
#include <QCheckBox>
#include <QSplitter>
#include <QMap>
#include <QList>
#include <QTimer>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KMessageBox>
#include <KGuiItem>
#include <KHelpClient>
#include <KSharedConfig>
#include <KActionCollection>
#include <KListWidgetSearchLine>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
+#include "mymoneyprice.h"
+#include "mymoneytransactionfilter.h"
#include "kmymoneyglobalsettings.h"
#include "kmymoney.h"
#include "models.h"
#include "accountsmodel.h"
#include "mymoneysecurity.h"
#include "icons.h"
using namespace Icons;
/* -------------------------------------------------------------------------*/
/* KTransactionPtrVector */
/* -------------------------------------------------------------------------*/
// *** KTagListItem Implementation ***
KTagListItem::KTagListItem(QListWidget *parent, const MyMoneyTag& tag) :
QListWidgetItem(parent, QListWidgetItem::UserType),
m_tag(tag)
{
setText(tag.name());
// allow in column rename
setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
}
KTagListItem::~KTagListItem()
{
}
// *** KTagsView Implementation ***
KTagsView::KTagsView(QWidget *parent) :
QWidget(parent),
m_needReload(false),
m_needLoad(true),
m_inSelection(false),
m_allowEditing(true),
m_tagFilterType(0)
{
}
KTagsView::~KTagsView()
{
if (!m_needLoad) {
// remember the splitter settings for startup
KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings");
grp.writeEntry("KTagsViewSplitterSize", m_splitter->saveState());
grp.sync();
}
}
void KTagsView::setDefaultFocus()
{
QTimer::singleShot(0, m_searchWidget, SLOT(setFocus()));
}
void KTagsView::init()
{
m_needLoad = false;
setupUi(this);
m_filterProxyModel = new AccountNamesFilterProxyModel(this);
- m_filterProxyModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Asset, MyMoneyAccount::Liability, MyMoneyAccount::Income, MyMoneyAccount::Expense});
+ m_filterProxyModel->addAccountGroup(QVector<eMyMoney::Account> {eMyMoney::Account::Asset, eMyMoney::Account::Liability, eMyMoney::Account::Income, eMyMoney::Account::Expense});
auto const model = Models::instance()->accountsModel();
m_filterProxyModel->setSourceModel(model);
m_filterProxyModel->setSourceColumns(model->getColumns());
m_filterProxyModel->sort((int)eAccountsModel::Column::Account);
// create the searchline widget
// and insert it into the existing layout
m_searchWidget = new KListWidgetSearchLine(this, m_tagsList);
m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
m_tagsList->setContextMenuPolicy(Qt::CustomContextMenu);
m_listTopHLayout->insertWidget(0, m_searchWidget);
//load the filter type
m_filterBox->addItem(i18nc("@item Show all tags", "All"));
m_filterBox->addItem(i18nc("@item Show only used tags", "Used"));
m_filterBox->addItem(i18nc("@item Show only unused tags", "Unused"));
m_filterBox->addItem(i18nc("@item Show only opened tags", "Opened"));
m_filterBox->addItem(i18nc("@item Show only closed tags", "Closed"));
m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
KGuiItem newButtonItem(QString(),
QIcon::fromTheme(g_Icons[Icon::ListAddTag]),
i18n("Creates a new tag"),
i18n("Use this to create a new tag."));
KGuiItem::assign(m_newButton, newButtonItem);
m_newButton->setToolTip(newButtonItem.toolTip());
KGuiItem renameButtonItem(QString(),
QIcon::fromTheme(g_Icons[Icon::EditRename]),
i18n("Rename the current selected tag"),
i18n("Use this to start renaming the selected tag."));
KGuiItem::assign(m_renameButton, renameButtonItem);
m_renameButton->setToolTip(renameButtonItem.toolTip());
KGuiItem deleteButtonItem(QString(),
QIcon::fromTheme(g_Icons[Icon::ListRemoveTag]),
i18n("Delete the current selected tag"),
i18n("Use this to delete the selected tag."));
KGuiItem::assign(m_deleteButton, deleteButtonItem);
m_deleteButton->setToolTip(deleteButtonItem.toolTip());
KGuiItem updateButtonItem(i18nc("Update tag", "Update"),
QIcon::fromTheme(g_Icons[Icon::DialogOK]),
i18n("Accepts the entered data and stores it"),
i18n("Use this to accept the modified data."));
KGuiItem::assign(m_updateButton, updateButtonItem);
m_updateButton->setEnabled(false);
QList<KMyMoneyRegister::Column> cols;
cols << KMyMoneyRegister::DateColumn;
cols << KMyMoneyRegister::AccountColumn;
cols << KMyMoneyRegister::DetailColumn;
cols << KMyMoneyRegister::ReconcileFlagColumn;
cols << KMyMoneyRegister::PaymentColumn;
cols << KMyMoneyRegister::DepositColumn;
m_register->setupRegister(MyMoneyAccount(), cols);
m_register->setSelectionMode(QTableWidget::SingleSelection);
m_register->setDetailsColumnType(KMyMoneyRegister::AccountFirst);
m_balanceLabel->hide();
connect(m_tagsList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(slotSelectTag(QListWidgetItem*,QListWidgetItem*)));
connect(m_tagsList, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectTag()));
connect(m_tagsList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotStartRename(QListWidgetItem*)));
connect(m_tagsList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(slotRenameTag(QListWidgetItem*)));
connect(m_tagsList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotOpenContextMenu(QPoint)));
connect(m_renameButton, SIGNAL(clicked()), this, SLOT(slotRenameButtonCliked()));
connect(m_deleteButton, SIGNAL(clicked()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TagDelete]), SLOT(trigger()));
connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotTagNew()));
connect(m_colorbutton, SIGNAL(changed(QColor)), this, SLOT(slotTagDataChanged()));
connect(m_closed, SIGNAL(stateChanged(int)), this, SLOT(slotTagDataChanged()));
connect(m_notes, SIGNAL(textChanged()), this, SLOT(slotTagDataChanged()));
connect(m_updateButton, SIGNAL(clicked()), this, SLOT(slotUpdateTag()));
connect(m_helpButton, SIGNAL(clicked()), this, SLOT(slotHelp()));
connect(m_register, SIGNAL(editTransaction()), this, SLOT(slotSelectTransaction()));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadTags()));
connect(m_filterBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeFilter(int)));
// use the size settings of the last run (if any)
KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings");
m_splitter->restoreState(grp.readEntry("KTagsViewSplitterSize", QByteArray()));
m_splitter->setChildrenCollapsible(false);
// At start we haven't any tag selected
m_tabWidget->setEnabled(false); // disable tab widget
m_deleteButton->setEnabled(false); // disable delete and rename button
m_renameButton->setEnabled(false);
m_tag = MyMoneyTag(); // make sure we don't access an undefined tag
clearItemData();
}
void KTagsView::slotStartRename(QListWidgetItem* item)
{
m_allowEditing = true;
m_tagsList->editItem(item);
}
void KTagsView::slotRenameButtonCliked()
{
if (m_tagsList->currentItem() && m_tagsList->selectedItems().count() == 1) {
slotStartRename(m_tagsList->currentItem());
}
}
// This variant is only called when a single tag is selected and renamed.
void KTagsView::slotRenameTag(QListWidgetItem* ta)
{
//if there is no current item selected, exit
if (m_allowEditing == false || !m_tagsList->currentItem() || ta != m_tagsList->currentItem())
return;
//qDebug() << "[KTagsView::slotRenameTag]";
// create a copy of the new name without appended whitespaces
QString new_name = ta->text();
if (m_tag.name() != new_name) {
MyMoneyFileTransaction ft;
try {
// check if we already have a tag with the new name
try {
// this function call will throw an exception, if the tag
// hasn't been found.
MyMoneyFile::instance()->tagByName(new_name);
// the name already exists, ask the user whether he's sure to keep the name
if (KMessageBox::questionYesNo(this,
i18n("A tag with the name '%1' already exists. It is not advisable to have "
"multiple tags with the same identification name. Are you sure you would like "
"to rename the tag?", new_name)) != KMessageBox::Yes) {
ta->setText(m_tag.name());
return;
}
} catch (const MyMoneyException &) {
// all ok, the name is unique
}
m_tag.setName(new_name);
m_newName = new_name;
MyMoneyFile::instance()->modifyTag(m_tag);
// the above call to modifyTag will reload the view so
// all references and pointers to the view have to be
// re-established.
// make sure, that the record is visible even if it moved
// out of sight due to the rename operation
ensureTagVisible(m_tag.id());
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to modify tag"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
}
} else {
ta->setText(new_name);
}
}
void KTagsView::ensureTagVisible(const QString& id)
{
for (int i = 0; i < m_tagsList->count(); ++i) {
KTagListItem* ta = dynamic_cast<KTagListItem*>(m_tagsList->item(0));
if (ta && ta->tag().id() == id) {
m_tagsList->scrollToItem(ta, QAbstractItemView::PositionAtCenter);
m_tagsList->setCurrentItem(ta); // active item and deselect all others
m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it
break;
}
}
}
void KTagsView::selectedTags(QList<MyMoneyTag>& tagsList) const
{
QList<QListWidgetItem *> selectedItems = m_tagsList->selectedItems();
QList<QListWidgetItem *>::ConstIterator itemsIt = selectedItems.constBegin();
while (itemsIt != selectedItems.constEnd()) {
KTagListItem* item = dynamic_cast<KTagListItem*>(*itemsIt);
if (item)
tagsList << item->tag();
++itemsIt;
}
}
void KTagsView::slotSelectTag(QListWidgetItem* cur, QListWidgetItem* prev)
{
Q_UNUSED(cur);
Q_UNUSED(prev);
m_allowEditing = false;
}
void KTagsView::slotSelectTag()
{
// check if the content of a currently selected tag was modified
// and ask to store the data
if (m_updateButton->isEnabled()) {
if (KMessageBox::questionYesNo(this, QString("<qt>%1</qt>").arg(
i18n("Do you want to save the changes for <b>%1</b>?", m_newName)),
i18n("Save changes")) == KMessageBox::Yes) {
m_inSelection = true;
slotUpdateTag();
m_inSelection = false;
}
}
// loop over all tags and count the number of tags, also
// obtain last selected tag
QList<MyMoneyTag> tagsList;
selectedTags(tagsList);
emit selectObjects(tagsList);
if (tagsList.isEmpty()) {
m_tabWidget->setEnabled(false); // disable tab widget
m_balanceLabel->hide();
m_deleteButton->setEnabled(false); //disable delete and rename button
m_renameButton->setEnabled(false);
clearItemData();
m_tag = MyMoneyTag();
return; // make sure we don't access an undefined tag
}
m_deleteButton->setEnabled(true); //re-enable delete button
// if we have multiple tags selected, clear and disable the tag information
if (tagsList.count() > 1) {
m_tabWidget->setEnabled(false); // disable tab widget
m_renameButton->setEnabled(false); // disable also the rename button
m_balanceLabel->hide();
clearItemData();
} else m_renameButton->setEnabled(true);
// otherwise we have just one selected, enable tag information widget and renameButton
m_tabWidget->setEnabled(true);
m_balanceLabel->show();
// as of now we are updating only the last selected tag, and until
// selection mode of the QListView has been changed to Extended, this
// will also be the only selection and behave exactly as before - Andreas
try {
m_tag = tagsList[0];
m_newName = m_tag.name();
m_colorbutton->setEnabled(true);
m_colorbutton->setColor(m_tag.tagColor());
m_closed->setEnabled(true);
m_closed->setChecked(m_tag.isClosed());
m_notes->setEnabled(true);
m_notes->setText(m_tag.notes());
slotTagDataChanged();
showTransactions();
} catch (const MyMoneyException &e) {
qDebug("exception during display of tag: %s at %s:%ld", qPrintable(e.what()), qPrintable(e.file()), e.line());
m_register->clear();
m_tag = MyMoneyTag();
}
m_allowEditing = true;
}
void KTagsView::clearItemData()
{
m_colorbutton->setColor(QColor());
m_closed->setChecked(false);
m_notes->setText(QString());
showTransactions();
}
void KTagsView::showTransactions()
{
MyMoneyMoney balance;
MyMoneyFile *file = MyMoneyFile::instance();
MyMoneySecurity base = file->baseCurrency();
// setup sort order
m_register->setSortOrder(KMyMoneyGlobalSettings::sortSearchView());
// clear the register
m_register->clear();
if (m_tag.id().isEmpty() || !m_tabWidget->isEnabled()) {
m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction())));
return;
}
// setup the list and the pointer vector
MyMoneyTransactionFilter filter;
filter.addTag(m_tag.id());
filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate());
// retrieve the list from the engine
file->transactionList(m_transactionList, filter);
// create the elements for the register
QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
QMap<QString, int> uniqueMap;
MyMoneyMoney deposit, payment;
int splitCount = 0;
bool balanceAccurate = true;
for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) {
const MyMoneySplit& split = (*it).second;
MyMoneyAccount acc = file->account(split.accountId());
++splitCount;
uniqueMap[(*it).first.id()]++;
KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]);
// take care of foreign currencies
MyMoneyMoney val = split.shares().abs();
if (acc.currencyId() != base.id()) {
const MyMoneyPrice &price = file->price(acc.currencyId(), base.id());
// in case the price is valid, we use it. Otherwise, we keep
// a flag that tells us that the balance is somewhat inaccurate
if (price.isValid()) {
val *= price.rate(base.id());
} else {
balanceAccurate = false;
}
}
if (split.shares().isNegative()) {
payment += val;
} else {
deposit += val;
}
}
balance = deposit - payment;
// add the group markers
m_register->addGroupMarkers();
// sort the transactions according to the sort setting
m_register->sortItems();
// remove trailing and adjacent markers
m_register->removeUnwantedGroupMarkers();
m_register->updateRegister(true);
// we might end up here with updates disabled on the register so
// make sure that we enable updates here
m_register->setUpdatesEnabled(true);
m_balanceLabel->setText(i18n("Balance: %1%2",
balanceAccurate ? "" : "~",
balance.formatMoney(file->baseCurrency().smallestAccountFraction())));
}
void KTagsView::slotTagDataChanged()
{
bool rc = false;
if (m_tabWidget->isEnabled()) {
rc |= ((m_tag.tagColor().isValid() != m_colorbutton->color().isValid())
|| (m_colorbutton->color().isValid() && m_tag.tagColor() != m_colorbutton->color()));
rc |= (m_closed->isChecked() != m_tag.isClosed());
rc |= ((m_tag.notes().isEmpty() != m_notes->toPlainText().isEmpty())
|| (!m_notes->toPlainText().isEmpty() && m_tag.notes() != m_notes->toPlainText()));
}
m_updateButton->setEnabled(rc);
}
void KTagsView::slotUpdateTag()
{
if (m_updateButton->isEnabled()) {
MyMoneyFileTransaction ft;
m_updateButton->setEnabled(false);
try {
m_tag.setName(m_newName);
m_tag.setTagColor(m_colorbutton->color());
m_tag.setClosed(m_closed->isChecked());
m_tag.setNotes(m_notes->toPlainText());
MyMoneyFile::instance()->modifyTag(m_tag);
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to modify tag"),
i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line()));
}
}
}
void KTagsView::showEvent(QShowEvent* event)
{
if (m_needLoad)
init();
emit aboutToShow();
if (m_needReload) {
loadTags();
m_needReload = false;
}
// don't forget base class implementation
QWidget::showEvent(event);
QList<MyMoneyTag> list;
selectedTags(list);
emit selectObjects(list);
}
void KTagsView::slotLoadTags()
{
if (isVisible()) {
if (m_inSelection)
QTimer::singleShot(0, this, SLOT(slotLoadTags()));
else
loadTags();
} else {
m_needReload = true;
}
}
void KTagsView::loadTags()
{
if (m_inSelection)
return;
QMap<QString, bool> isSelected;
QString id;
MyMoneyFile* file = MyMoneyFile::instance();
// remember which items are selected in the list
QList<QListWidgetItem *> selectedItems = m_tagsList->selectedItems();
QList<QListWidgetItem *>::const_iterator tagsIt = selectedItems.constBegin();
while (tagsIt != selectedItems.constEnd()) {
KTagListItem* item = dynamic_cast<KTagListItem*>(*tagsIt);
if (item)
isSelected[item->tag().id()] = true;
++tagsIt;
}
// keep current selected item
KTagListItem *currentItem = static_cast<KTagListItem *>(m_tagsList->currentItem());
if (currentItem)
id = currentItem->tag().id();
m_allowEditing = false;
// clear the list
m_searchWidget->clear();
m_searchWidget->updateSearch();
m_tagsList->clear();
m_register->clear();
currentItem = 0;
QList<MyMoneyTag>list = file->tagList();
QList<MyMoneyTag>::ConstIterator it;
for (it = list.constBegin(); it != list.constEnd(); ++it) {
if (m_tagFilterType == eAllTags ||
(m_tagFilterType == eReferencedTags && file->isReferenced(*it)) ||
(m_tagFilterType == eUnusedTags && !file->isReferenced(*it)) ||
(m_tagFilterType == eOpenedTags && !(*it).isClosed()) ||
(m_tagFilterType == eClosedTags && (*it).isClosed())) {
KTagListItem* item = new KTagListItem(m_tagsList, *it);
if (item->tag().id() == id)
currentItem = item;
if (isSelected[item->tag().id()])
item->setSelected(true);
}
}
m_tagsList->sortItems();
if (currentItem) {
m_tagsList->setCurrentItem(currentItem);
m_tagsList->scrollToItem(currentItem);
}
m_filterProxyModel->invalidate();
slotSelectTag(0, 0);
m_allowEditing = true;
}
void KTagsView::slotSelectTransaction()
{
QList<KMyMoneyRegister::RegisterItem*> list = m_register->selectedItems();
if (!list.isEmpty()) {
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(list[0]);
if (t)
emit transactionSelected(t->split().accountId(), t->transaction().id());
}
}
void KTagsView::slotSelectTagAndTransaction(const QString& tagId, const QString& accountId, const QString& transactionId)
{
if (!isVisible())
return;
try {
// clear filter
m_searchWidget->clear();
m_searchWidget->updateSearch();
// deselect all other selected items
QList<QListWidgetItem *> selectedItems = m_tagsList->selectedItems();
QList<QListWidgetItem *>::const_iterator tagsIt = selectedItems.constBegin();
while (tagsIt != selectedItems.constEnd()) {
KTagListItem* item = dynamic_cast<KTagListItem*>(*tagsIt);
if (item)
item->setSelected(false);
++tagsIt;
}
// find the tag in the list
QListWidgetItem* it;
for (int i = 0; i < m_tagsList->count(); ++i) {
it = m_tagsList->item(i);
KTagListItem* item = dynamic_cast<KTagListItem *>(it);
if (item && item->tag().id() == tagId) {
m_tagsList->scrollToItem(it, QAbstractItemView::PositionAtCenter);
m_tagsList->setCurrentItem(it); // active item and deselect all others
m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it
//make sure the tag selection is updated and transactions are updated accordingly
slotSelectTag();
KMyMoneyRegister::RegisterItem *item = 0;
for (int i = 0; i < m_register->rowCount(); ++i) {
item = m_register->itemAtRow(i);
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(item);
if (t) {
if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) {
m_register->selectItem(item);
m_register->ensureItemVisible(item);
break;
}
}
}
// quit out of for() loop
break;
}
}
} catch (const MyMoneyException &e) {
qWarning("Unexpected exception in KTagsView::slotSelectTagAndTransaction %s", qPrintable(e.what()));
}
}
void KTagsView::slotOpenContextMenu(const QPoint& /*ta*/)
{
KTagListItem* item = dynamic_cast<KTagListItem*>(m_tagsList->currentItem());
if (item) {
slotSelectTag();
emit openContextMenu(item->tag());
}
}
void KTagsView::slotTagNew()
{
kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TagNew])->trigger();
}
void KTagsView::slotHelp()
{
KHelpClient::invokeHelp("details.tags.attributes");
//FIXME-ALEX update help file
}
void KTagsView::slotChangeFilter(int index)
{
//update the filter type then reload the tags list
m_tagFilterType = index;
loadTags();
}
diff --git a/kmymoney/views/ledgerview.cpp b/kmymoney/views/ledgerview.cpp
index 68f6bd5cd..1043ff12a 100644
--- a/kmymoney/views/ledgerview.cpp
+++ b/kmymoney/views/ledgerview.cpp
@@ -1,440 +1,440 @@
/***************************************************************************
ledgerview.cpp
-------------------
begin : Sat Aug 8 2015
copyright : (C) 2015 by Thomas Baumgart
email : Thomas Baumgart <tbaumgart@kde.org>
***************************************************************************/
/***************************************************************************
* *
* 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 "ledgerview.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QHeaderView>
#include <QPainter>
#include <QResizeEvent>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "ledgerdelegate.h"
#include "ledgermodel.h"
#include "models.h"
class LedgerView::Private
{
public:
Private(LedgerView* p)
: delegate(0)
, filterModel(new LedgerSortFilterProxyModel(p))
, adjustableColumn(LedgerModel::DetailColumn)
, adjustingColumn(false)
, showValuesInverted(false)
, balanceCalculationPending(false)
{
filterModel->setFilterRole(LedgerRole::AccountIdRole);
filterModel->setSourceModel(Models::instance()->ledgerModel());
}
void setDelegate(LedgerDelegate* _delegate)
{
delete delegate;
delegate = _delegate;
}
void setSortRole(LedgerRole::Roles role, int column)
{
Q_ASSERT(delegate);
Q_ASSERT(filterModel);
delegate->setSortRole(role);
filterModel->setSortRole(role);
filterModel->sort(column);
}
void recalculateBalances()
{
QModelIndex start = filterModel->index(0, 0);
QModelIndexList indexes = filterModel->match(start, LedgerRole::AccountIdRole, account.id(), -1);
MyMoneyMoney balance;
Q_FOREACH(QModelIndex index, indexes) {
if(showValuesInverted) {
balance -= filterModel->data(index, LedgerRole::SplitSharesRole).value<MyMoneyMoney>();
} else {
balance += filterModel->data(index, LedgerRole::SplitSharesRole).value<MyMoneyMoney>();
}
QString txt = balance.formatMoney(account.fraction());
QModelIndex dispIndex = filterModel->index(index.row(), LedgerModel::BalanceColumn);
filterModel->setData(dispIndex, txt, Qt::DisplayRole);
}
filterModel->invalidate();
balanceCalculationPending = false;
}
LedgerDelegate* delegate;
LedgerSortFilterProxyModel* filterModel;
MyMoneyAccount account;
int adjustableColumn;
bool adjustingColumn;
bool showValuesInverted;
bool balanceCalculationPending;
};
LedgerView::LedgerView(QWidget* parent)
: QTableView(parent)
, d(new Private(this))
{
verticalHeader()->setDefaultSectionSize(15);
verticalHeader()->setMinimumSectionSize(15);
verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
verticalHeader()->hide();
// since we don't have a vertical header, it does not make sense
// to use the first column to select all items in the view
setCornerButtonEnabled(false);
// This will allow the user to move the columns, but
// the delegate cannot handle it yet and it requires to
// reset the spans as well.
// horizontalHeader()->setMovable(true);
// make sure to get informed about resize operations on the columns
connect(horizontalHeader(), SIGNAL(sectionResized(int,int,int)), this, SLOT(adjustDetailColumn()));
// we don't need autoscroll as we do not support drag/drop
setAutoScroll(false);
setAlternatingRowColors(true);
setSelectionBehavior(SelectRows);
setTabKeyNavigation(false);
setModel(d->filterModel);
}
LedgerView::~LedgerView()
{
delete d;
}
bool LedgerView::showValuesInverted() const
{
return d->showValuesInverted;
}
void LedgerView::setAccount(const MyMoneyAccount& acc)
{
d->account = acc;
switch(acc.accountType()) {
- case MyMoneyAccount::Investment:
+ case eMyMoney::Account::Investment:
break;
default:
setColumnHidden(LedgerModel::SecurityColumn, true);
setColumnHidden(LedgerModel::CostCenterColumn, true);
setColumnHidden(LedgerModel::QuantityColumn, true);
setColumnHidden(LedgerModel::PriceColumn, true);
setColumnHidden(LedgerModel::AmountColumn, true);
setColumnHidden(LedgerModel::ValueColumn, true);
horizontalHeader()->resizeSection(LedgerModel::ReconciliationColumn, 20);
d->setDelegate(new LedgerDelegate(this));
setItemDelegate(d->delegate);
break;
}
d->showValuesInverted = false;
- if(acc.accountGroup() == MyMoneyAccount::Liability
- || acc.accountGroup() == MyMoneyAccount::Income) {
+ if(acc.accountGroup() == eMyMoney::Account::Liability
+ || acc.accountGroup() == eMyMoney::Account::Income) {
d->showValuesInverted = true;
}
d->filterModel->setFilterFixedString(acc.id());
d->filterModel->setAccountType(acc.accountType());
d->setSortRole(LedgerRole::PostDateRole, LedgerModel::DateColumn);
// if balance calculation has not been triggered, then run it immediately
if(!d->balanceCalculationPending) {
recalculateBalances();
}
if(d->filterModel->rowCount() > 0) {
// we need to check that the last row may contain a scheduled transaction
// in that case, we need to go back to find the actual last transaction
int row = d->filterModel->rowCount()-1;
while(row >= 0) {
QModelIndex index = d->filterModel->index(row, 0);
if(index.model()->data(index, LedgerRole::ScheduleIdRole).toString().isEmpty()) {
setCurrentIndex(index);
selectRow(index.row());
scrollTo(index, PositionAtBottom);
break;
}
row--;
}
}
}
QString LedgerView::accountId() const
{
QString id;
if(d->filterModel->filterRole() == LedgerRole::AccountIdRole)
id = d->account.id();
return id;
}
void LedgerView::recalculateBalances()
{
d->recalculateBalances();
}
void LedgerView::rowsAboutToBeRemoved(const QModelIndex& index, int start, int end)
{
QAbstractItemView::rowsAboutToBeRemoved(index, start, end);
// make sure the balances are recalculated but trigger only once
if(!d->balanceCalculationPending) {
d->balanceCalculationPending = true;
QMetaObject::invokeMethod(this, "recalculateBalances", Qt::QueuedConnection);
}
}
void LedgerView::rowsInserted(const QModelIndex& index, int start, int end)
{
QTableView::rowsInserted(index, start, end);
// make sure the balances are recalculated but trigger only once
if(!d->balanceCalculationPending) {
d->balanceCalculationPending = true;
QMetaObject::invokeMethod(this, "recalculateBalances", Qt::QueuedConnection);
}
}
bool LedgerView::edit(const QModelIndex& index, QAbstractItemView::EditTrigger trigger, QEvent* event)
{
bool rc = QTableView::edit(index, trigger, event);
if(rc) {
// editing started, but we need the editor to cover all columns
// so we close it, set the span to have a single row and recreate
// the editor in that single cell
closeEditor(indexWidget(index), QAbstractItemDelegate::NoHint);
bool haveEditorInOtherView = false;
/// @todo Here we need to make sure that only a single editor can be started at a time
if(!haveEditorInOtherView) {
emit aboutToStartEdit();
setSpan(index.row(), 0, 1, horizontalHeader()->count());
QModelIndex editIndex = model()->index(index.row(), 0);
rc = QTableView::edit(editIndex, trigger, event);
// make sure that the row gets resized according to the requirements of the editor
// and is completely visible
resizeRowToContents(index.row());
QMetaObject::invokeMethod(this, "scrollTo", Q_ARG(QModelIndex, index), Q_ARG(ScrollHint, EnsureVisible));
} else {
rc = false;
}
}
return rc;
}
void LedgerView::closeEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint)
{
QTableView::closeEditor(editor, hint);
clearSpans();
// we need to resize the row that contained the editor.
resizeRowsToContents();
emit aboutToFinishEdit();
ensureCurrentItemIsVisible();
}
void LedgerView::mousePressEvent(QMouseEvent* event)
{
// qDebug() << "mousePressEvent";
if(state() != QAbstractItemView::EditingState) {
QTableView::mousePressEvent(event);
}
}
void LedgerView::mouseMoveEvent(QMouseEvent* event)
{
// qDebug() << "mouseMoveEvent";
QTableView::mouseMoveEvent(event);
}
void LedgerView::mouseDoubleClickEvent(QMouseEvent* event)
{
// qDebug() << "mouseDoubleClickEvent";
QTableView::mouseDoubleClickEvent(event);
}
void LedgerView::wheelEvent(QWheelEvent* e)
{
// qDebug() << "wheelEvent";
QTableView::wheelEvent(e);
}
void LedgerView::currentChanged(const QModelIndex& current, const QModelIndex& previous)
{
// qDebug() << "currentChanged";
QTableView::currentChanged(current, previous);
if(current.isValid()) {
QModelIndex index = current.model()->index(current.row(), 0);
scrollTo(index, EnsureVisible);
QString id = current.model()->data(index, LedgerRole::TransactionSplitIdRole).toString();
// For a new transaction the id is completely empty, for a split view the transaction
// part is filled but the split id is empty and the string ends with a dash
if(id.isEmpty() || id.endsWith('-')) {
edit(index);
} else {
emit transactionSelected(id);
}
QMetaObject::invokeMethod(this, "doItemsLayout", Qt::QueuedConnection);
}
}
void LedgerView::moveEvent(QMoveEvent* event)
{
// qDebug() << "moveEvent";
QWidget::moveEvent(event);
}
void LedgerView::paintEvent(QPaintEvent* event)
{
QTableView::paintEvent(event);
// the base class implementation paints the regular grid in case there
// is room below the last line and the bottom of the viewport. We check
// here if that is the case and fill that part with the base color to
// remove the false painted grid.
const QHeaderView *verticalHeader = this->verticalHeader();
if(verticalHeader->count() == 0)
return;
int lastVisualRow = verticalHeader->visualIndexAt(verticalHeader->viewport()->height());
if (lastVisualRow == -1)
lastVisualRow = model()->rowCount(QModelIndex()) - 1;
while(lastVisualRow >= model()->rowCount(QModelIndex()))
--lastVisualRow;
while ((lastVisualRow > -1) && verticalHeader->isSectionHidden(verticalHeader->logicalIndex(lastVisualRow)))
--lastVisualRow;
int top = 0;
if(lastVisualRow != -1)
top = verticalHeader->sectionViewportPosition(lastVisualRow) + verticalHeader->sectionSize(lastVisualRow);
if(top < viewport()->height()) {
QPainter painter(viewport());
QRect rect(0, top, viewport()->width(), viewport()->height()-top);
painter.fillRect(rect, QBrush(palette().base()));
}
}
int LedgerView::sizeHintForRow(int row) const
{
// we can optimize the sizeHintForRow() operation by asking the
// delegate about the height. There's no need to use the std
// method which scans over all items in a column and takes a long
// time in large ledgers. In case the editor is open in the row, we
// use the regular method.
QModelIndex index = d->filterModel->index(row, 0);
if(d->delegate && (d->delegate->editorRow() != row)) {
QStyleOptionViewItem opt;
int hint = d->delegate->sizeHint(opt, index).height();
if(showGrid())
hint += 1;
return hint;
}
return QTableView::sizeHintForRow(row);
}
void LedgerView::resizeEvent(QResizeEvent* event)
{
// qDebug() << "resizeEvent, old:" << event->oldSize() << "new:" << event->size() << "viewport:" << viewport()->width();
QTableView::resizeEvent(event);
adjustDetailColumn(event->size().width());
}
void LedgerView::adjustDetailColumn()
{
adjustDetailColumn(viewport()->width());
}
void LedgerView::adjustDetailColumn(int newViewportWidth)
{
// make sure we don't get here recursively
if(d->adjustingColumn)
return;
d->adjustingColumn = true;
QHeaderView* header = horizontalHeader();
int totalColumnWidth = 0;
for(int i=0; i < header->count(); ++i) {
if(header->isSectionHidden(i)) {
continue;
}
totalColumnWidth += header->sectionSize(i);
}
const int delta = newViewportWidth - totalColumnWidth;
const int newWidth = header->sectionSize(d->adjustableColumn) + delta;
if(newWidth > 10) {
header->resizeSection(d->adjustableColumn, newWidth);
}
// remember that we're done this time
d->adjustingColumn = false;
}
void LedgerView::scrollTo(const QModelIndex& index, QAbstractItemView::ScrollHint hint)
{
QTableView::scrollTo(index, hint);
}
void LedgerView::ensureCurrentItemIsVisible()
{
QMetaObject::invokeMethod(this, "scrollTo", Q_ARG(QModelIndex, currentIndex()), Q_ARG(ScrollHint, EnsureVisible));
}
void LedgerView::setShowEntryForNewTransaction(bool show)
{
d->filterModel->setShowNewTransaction(show);
}
SplitView::SplitView(QWidget* parent)
: LedgerView(parent)
{
}
SplitView::~SplitView()
{
}
diff --git a/kmymoney/views/ledgerviewpage.cpp b/kmymoney/views/ledgerviewpage.cpp
index e50720741..028ebb09c 100644
--- a/kmymoney/views/ledgerviewpage.cpp
+++ b/kmymoney/views/ledgerviewpage.cpp
@@ -1,147 +1,147 @@
/***************************************************************************
ledgerviewpage.cpp
-------------------
begin : Sat Aug 8 2015
copyright : (C) 2015 by Thomas Baumgart
email : Thomas Baumgart <tbaumgart@kde.org>
***************************************************************************/
/***************************************************************************
* *
* 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 "ledgerviewpage.h"
#include "mymoneyaccount.h"
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "newtransactionform.h"
#include "models.h"
#include "ledgermodel.h"
#include "ui_ledgerviewpage.h"
class LedgerViewPage::Private
{
public:
Private(QWidget* parent)
: ui(new Ui_LedgerViewPage)
, form(0)
{
ui->setupUi(parent);
// make sure, we can disable the detail form but not the ledger view
ui->splitter->setCollapsible(0, false);
ui->splitter->setCollapsible(1, true);
// make sure the ledger gets all the stretching
ui->splitter->setStretchFactor(0, 3);
ui->splitter->setStretchFactor(1, 1);
ui->splitter->setSizes(QList<int>() << 10000 << ui->formWidget->sizeHint().height());
}
Ui_LedgerViewPage* ui;
NewTransactionForm* form;
QSet<QString> hideFormReasons;
};
LedgerViewPage::LedgerViewPage(QWidget* parent)
: QWidget(parent)
, d(new Private(this))
{
connect(d->ui->ledgerView, &LedgerView::transactionSelected, this, &LedgerViewPage::transactionSelected);
connect(d->ui->ledgerView, &LedgerView::aboutToStartEdit, this, &LedgerViewPage::aboutToStartEdit);
connect(d->ui->ledgerView, &LedgerView::aboutToFinishEdit, this, &LedgerViewPage::aboutToFinishEdit);
connect(d->ui->ledgerView, &LedgerView::aboutToStartEdit, this, &LedgerViewPage::startEdit);
connect(d->ui->ledgerView, &LedgerView::aboutToFinishEdit, this, &LedgerViewPage::finishEdit);
connect(d->ui->splitter, &QSplitter::splitterMoved, this, &LedgerViewPage::splitterChanged);
}
LedgerViewPage::~LedgerViewPage()
{
delete d;
}
QString LedgerViewPage::accountId() const
{
return d->ui->ledgerView->accountId();
}
void LedgerViewPage::setAccount(const MyMoneyAccount& acc)
{
// get rid of current form
delete d->form;
d->form = 0;
d->hideFormReasons.insert(QLatin1String("FormAvailable"));
switch(acc.accountType()) {
- case MyMoneyAccount::Investment:
+ case eMyMoney::Account::Investment:
break;
default:
d->form = new NewTransactionForm(d->ui->formWidget);
break;
}
if(d->form) {
d->hideFormReasons.remove(QLatin1String("FormAvailable"));
// make sure we have a layout
if(!d->ui->formWidget->layout()) {
d->ui->formWidget->setLayout(new QHBoxLayout(d->ui->formWidget));
}
d->ui->formWidget->layout()->addWidget(d->form);
connect(d->ui->ledgerView, &LedgerView::transactionSelected,
d->form, &NewTransactionForm::showTransaction);
connect(Models::instance()->ledgerModel(), &LedgerModel::dataChanged,
d->form, &NewTransactionForm::modelDataChanged);
}
d->ui->formWidget->setVisible(d->hideFormReasons.isEmpty());
d->ui->ledgerView->setAccount(acc);
}
void LedgerViewPage::showTransactionForm(bool show)
{
if(show) {
d->hideFormReasons.remove(QLatin1String("General"));
} else {
d->hideFormReasons.insert(QLatin1String("General"));
}
d->ui->formWidget->setVisible(d->hideFormReasons.isEmpty());
}
void LedgerViewPage::startEdit()
{
d->hideFormReasons.insert(QLatin1String("Edit"));
d->ui->formWidget->hide();
}
void LedgerViewPage::finishEdit()
{
d->hideFormReasons.remove(QLatin1String("Edit"));
d->ui->formWidget->setVisible(d->hideFormReasons.isEmpty());
// the focus should be on the ledger view once editing ends
d->ui->ledgerView->setFocus();
}
void LedgerViewPage::splitterChanged(int pos, int index)
{
Q_UNUSED(pos);
Q_UNUSED(index);
d->ui->ledgerView->ensureCurrentItemIsVisible();
}
void LedgerViewPage::setShowEntryForNewTransaction(bool show)
{
d->ui->ledgerView->setShowEntryForNewTransaction(show);
}
diff --git a/kmymoney/views/newspliteditor.cpp b/kmymoney/views/newspliteditor.cpp
index 403876dd7..58b36a539 100644
--- a/kmymoney/views/newspliteditor.cpp
+++ b/kmymoney/views/newspliteditor.cpp
@@ -1,380 +1,380 @@
/***************************************************************************
newspliteditor.cpp
-------------------
begin : Sat Apr 9 2016
copyright : (C) 2016 by Thomas Baumgart
email : Thomas Baumgart <tbaumgart@kde.org>
***************************************************************************/
/***************************************************************************
* *
* 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 "newspliteditor.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QCompleter>
#include <QSortFilterProxyModel>
#include <QStringList>
#include <QDebug>
#include <QStandardItemModel>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneyutils.h"
#include "kmymoneyaccountcombo.h"
#include "models.h"
#include "accountsmodel.h"
#include "costcentermodel.h"
#include "ledgermodel.h"
#include "mymoneysplit.h"
#include "ui_newspliteditor.h"
#include "widgethintframe.h"
#include "ledgerview.h"
#include "icons/icons.h"
using namespace Icons;
struct NewSplitEditor::Private
{
Private(NewSplitEditor* parent)
: ui(new Ui_NewSplitEditor)
, accountsModel(new AccountNamesFilterProxyModel(parent))
, costCenterModel(new QSortFilterProxyModel(parent))
, splitModel(0)
, accepted(false)
, costCenterRequired(false)
, costCenterOk(false)
, showValuesInverted(false)
{
accountsModel->setObjectName("AccountNamesFilterProxyModel");
costCenterModel->setObjectName("SortedCostCenterModel");
statusModel.setObjectName("StatusModel");
costCenterModel->setSortLocaleAware(true);
costCenterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
createStatusEntry(MyMoneySplit::NotReconciled);
createStatusEntry(MyMoneySplit::Cleared);
createStatusEntry(MyMoneySplit::Reconciled);
// createStatusEntry(MyMoneySplit::Frozen);
}
void createStatusEntry(MyMoneySplit::reconcileFlagE status);
bool checkForValidSplit(bool doUserInteraction = true);
bool costCenterChanged(int costCenterIndex);
bool categoryChanged(const QString& accountId);
bool numberChanged(const QString& newNumber);
bool amountChanged(CreditDebitHelper* valueHelper);
Ui_NewSplitEditor* ui;
AccountNamesFilterProxyModel* accountsModel;
QSortFilterProxyModel* costCenterModel;
SplitModel* splitModel;
bool accepted;
bool costCenterRequired;
bool costCenterOk;
bool showValuesInverted;
QStandardItemModel statusModel;
QString transactionSplitId;
MyMoneyAccount counterAccount;
MyMoneyAccount category;
CreditDebitHelper* amountHelper;
};
void NewSplitEditor::Private::createStatusEntry(MyMoneySplit::reconcileFlagE status)
{
QStandardItem* p = new QStandardItem(KMyMoneyUtils::reconcileStateToString(status, true));
p->setData(status);
statusModel.appendRow(p);
}
bool NewSplitEditor::Private::checkForValidSplit(bool doUserInteraction)
{
QStringList infos;
bool rc = true;
if(!costCenterChanged(ui->costCenterCombo->currentIndex())) {
infos << ui->costCenterCombo->toolTip();
rc = false;
}
if(doUserInteraction) {
/// @todo add dialog here that shows the @a infos
}
return rc;
}
bool NewSplitEditor::Private::costCenterChanged(int costCenterIndex)
{
bool rc = true;
WidgetHintFrame::hide(ui->costCenterCombo, i18n("The cost center this transaction should be assigned to."));
if(costCenterIndex != -1) {
if(costCenterRequired && ui->costCenterCombo->currentText().isEmpty()) {
WidgetHintFrame::show(ui->costCenterCombo, i18n("A cost center assignment is required for a transaction in the selected category."));
rc = false;
}
}
return rc;
}
bool NewSplitEditor::Private::categoryChanged(const QString& accountId)
{
bool rc = true;
if(!accountId.isEmpty()) {
try {
QModelIndex index = Models::instance()->accountsModel()->accountById(accountId);
category = Models::instance()->accountsModel()->data(index, (int)eAccountsModel::Role::Account).value<MyMoneyAccount>();
const bool isIncomeExpense = category.isIncomeExpense();
ui->costCenterCombo->setEnabled(isIncomeExpense);
ui->costCenterLabel->setEnabled(isIncomeExpense);
ui->numberEdit->setDisabled(isIncomeExpense);
ui->numberLabel->setDisabled(isIncomeExpense);
costCenterRequired = category.isCostCenterRequired();
rc &= costCenterChanged(ui->costCenterCombo->currentIndex());
} catch (MyMoneyException &e) {
qDebug() << "Ooops: invalid account id" << accountId << "in" << Q_FUNC_INFO;
}
}
return rc;
}
bool NewSplitEditor::Private::numberChanged(const QString& newNumber)
{
bool rc = true;
WidgetHintFrame::hide(ui->numberEdit, i18n("The check number used for this transaction."));
if(!newNumber.isEmpty()) {
const LedgerModel* model = Models::instance()->ledgerModel();
QModelIndexList list = model->match(model->index(0, 0), LedgerRole::NumberRole,
QVariant(newNumber),
-1, // all splits
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
foreach(QModelIndex index, list) {
if(model->data(index, LedgerRole::AccountIdRole) == ui->accountCombo->getSelected()
&& model->data(index, LedgerRole::TransactionSplitIdRole) != transactionSplitId) {
WidgetHintFrame::show(ui->numberEdit, i18n("The check number <b>%1</b> has already been used in this account.").arg(newNumber));
rc = false;
break;
}
}
}
return rc;
}
bool NewSplitEditor::Private::amountChanged(CreditDebitHelper* valueHelper)
{
Q_UNUSED(valueHelper);
bool rc = true;
return rc;
}
NewSplitEditor::NewSplitEditor(QWidget* parent, const QString& counterAccountId)
: QFrame(parent, Qt::FramelessWindowHint /* | Qt::X11BypassWindowManagerHint */)
, d(new Private(this))
{
SplitView* view = qobject_cast<SplitView*>(parent->parentWidget());
Q_ASSERT(view != 0);
d->splitModel = qobject_cast<SplitModel*>(view->model());
QModelIndex index = Models::instance()->accountsModel()->accountById(counterAccountId);
d->counterAccount = Models::instance()->accountsModel()->data(index, (int)eAccountsModel::Role::Account).value<MyMoneyAccount>();
d->ui->setupUi(this);
d->ui->enterButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogOK]));
d->ui->cancelButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogCancel]));
- d->accountsModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Asset, MyMoneyAccount::Liability, MyMoneyAccount::Income, MyMoneyAccount::Expense, MyMoneyAccount::Equity});
+ d->accountsModel->addAccountGroup(QVector<eMyMoney::Account> {eMyMoney::Account::Asset, eMyMoney::Account::Liability, eMyMoney::Account::Income, eMyMoney::Account::Expense, eMyMoney::Account::Equity});
d->accountsModel->setHideEquityAccounts(false);
auto const model = Models::instance()->accountsModel();
d->accountsModel->setSourceModel(model);
d->accountsModel->setSourceColumns(model->getColumns());
d->accountsModel->sort((int)eAccountsModel::Column::Account);
d->ui->accountCombo->setModel(d->accountsModel);
d->costCenterModel->setSortRole(Qt::DisplayRole);
d->costCenterModel->setSourceModel(Models::instance()->costCenterModel());
d->costCenterModel->sort((int)eAccountsModel::Column::Account);
d->ui->costCenterCombo->setEditable(true);
d->ui->costCenterCombo->setModel(d->costCenterModel);
d->ui->costCenterCombo->setModelColumn(0);
d->ui->costCenterCombo->completer()->setFilterMode(Qt::MatchContains);
WidgetHintFrameCollection* frameCollection = new WidgetHintFrameCollection(this);
frameCollection->addFrame(new WidgetHintFrame(d->ui->costCenterCombo));
frameCollection->addFrame(new WidgetHintFrame(d->ui->numberEdit, WidgetHintFrame::Warning));
frameCollection->addWidget(d->ui->enterButton);
d->amountHelper = new CreditDebitHelper(this, d->ui->amountEditCredit, d->ui->amountEditDebit);
connect(d->ui->numberEdit, SIGNAL(textChanged(QString)), this, SLOT(numberChanged(QString)));
connect(d->ui->costCenterCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(costCenterChanged(int)));
connect(d->ui->accountCombo, SIGNAL(accountSelected(QString)), this, SLOT(categoryChanged(QString)));
connect(d->amountHelper, SIGNAL(valueChanged()), this, SLOT(amountChanged()));
connect(d->ui->cancelButton, SIGNAL(clicked(bool)), this, SLOT(reject()));
connect(d->ui->enterButton, SIGNAL(clicked(bool)), this, SLOT(acceptEdit()));
}
NewSplitEditor::~NewSplitEditor()
{
}
void NewSplitEditor::setShowValuesInverted(bool inverse)
{
d->showValuesInverted = inverse;
}
bool NewSplitEditor::showValuesInverted()
{
return d->showValuesInverted;
}
bool NewSplitEditor::accepted() const
{
return d->accepted;
}
void NewSplitEditor::acceptEdit()
{
if(d->checkForValidSplit()) {
d->accepted = true;
emit done();
}
}
void NewSplitEditor::reject()
{
emit done();
}
void NewSplitEditor::keyPressEvent(QKeyEvent* e)
{
if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) {
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
{
if(focusWidget() == d->ui->cancelButton) {
reject();
} else {
if(d->ui->enterButton->isEnabled()) {
d->ui->enterButton->click();
}
return;
}
}
break;
case Qt::Key_Escape:
reject();
break;
default:
e->ignore();
return;
}
} else {
e->ignore();
}
}
QString NewSplitEditor::accountId() const
{
return d->ui->accountCombo->getSelected();
}
void NewSplitEditor::setAccountId(const QString& id)
{
d->ui->accountCombo->clearEditText();
d->ui->accountCombo->setSelected(id);
}
QString NewSplitEditor::memo() const
{
return d->ui->memoEdit->toPlainText();
}
void NewSplitEditor::setMemo(const QString& memo)
{
d->ui->memoEdit->setPlainText(memo);
}
MyMoneyMoney NewSplitEditor::amount() const
{
return d->amountHelper->value();
}
void NewSplitEditor::setAmount(MyMoneyMoney value)
{
d->amountHelper->setValue(value);
}
QString NewSplitEditor::costCenterId() const
{
const int row = d->ui->costCenterCombo->currentIndex();
QModelIndex index = d->ui->costCenterCombo->model()->index(row, 0);
return d->ui->costCenterCombo->model()->data(index, CostCenterModel::CostCenterIdRole).toString();
}
void NewSplitEditor::setCostCenterId(const QString& id)
{
QModelIndex index = Models::indexById(d->costCenterModel, CostCenterModel::CostCenterIdRole, id);
if(index.isValid()) {
d->ui->costCenterCombo->setCurrentIndex(index.row());
}
}
QString NewSplitEditor::number() const
{
return d->ui->numberEdit->text();
}
void NewSplitEditor::setNumber(const QString& number)
{
d->ui->numberEdit->setText(number);
}
QString NewSplitEditor::splitId() const
{
return d->transactionSplitId;
}
void NewSplitEditor::numberChanged(const QString& newNumber)
{
d->numberChanged(newNumber);
}
void NewSplitEditor::categoryChanged(const QString& accountId)
{
d->categoryChanged(accountId);
}
void NewSplitEditor::costCenterChanged(int costCenterIndex)
{
d->costCenterChanged(costCenterIndex);
}
void NewSplitEditor::amountChanged()
{
d->amountChanged(d->amountHelper);
}
diff --git a/kmymoney/views/newtransactioneditor.cpp b/kmymoney/views/newtransactioneditor.cpp
index 52934d8c1..b8ca76edc 100644
--- a/kmymoney/views/newtransactioneditor.cpp
+++ b/kmymoney/views/newtransactioneditor.cpp
@@ -1,695 +1,697 @@
/***************************************************************************
newtransactioneditor.cpp
-------------------
begin : Sat Aug 8 2015
copyright : (C) 2015 by Thomas Baumgart
email : Thomas Baumgart <tbaumgart@kde.org>
***************************************************************************/
/***************************************************************************
* *
* 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 "newtransactioneditor.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QCompleter>
#include <QSortFilterProxyModel>
#include <QStringList>
#include <QDebug>
#include <QGlobalStatic>
#include <QStandardItemModel>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
+#include "mymoneyfile.h"
+#include "mymoneyaccount.h"
#include "kmymoneyutils.h"
#include "kmymoneyaccountcombo.h"
#include "models.h"
#include "accountsmodel.h"
#include "costcentermodel.h"
#include "ledgermodel.h"
#include "payeesmodel.h"
#include "mymoneysplit.h"
#include "ui_newtransactioneditor.h"
#include "splitdialog.h"
#include "widgethintframe.h"
#include "icons/icons.h"
using namespace Icons;
Q_GLOBAL_STATIC(QDate, lastUsedPostDate)
class NewTransactionEditor::Private
{
public:
Private(NewTransactionEditor* parent)
: ui(new Ui_NewTransactionEditor)
, accountsModel(new AccountNamesFilterProxyModel(parent))
, costCenterModel(new QSortFilterProxyModel(parent))
, payeesModel(new QSortFilterProxyModel(parent))
, accepted(false)
, costCenterRequired(false)
{
accountsModel->setObjectName("NewTransactionEditor::accountsModel");
costCenterModel->setObjectName("SortedCostCenterModel");
payeesModel->setObjectName("SortedPayeesModel");
statusModel.setObjectName("StatusModel");
splitModel.setObjectName("SplitModel");
costCenterModel->setSortLocaleAware(true);
costCenterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
payeesModel->setSortLocaleAware(true);
payeesModel->setSortCaseSensitivity(Qt::CaseInsensitive);
createStatusEntry(MyMoneySplit::NotReconciled);
createStatusEntry(MyMoneySplit::Cleared);
createStatusEntry(MyMoneySplit::Reconciled);
// createStatusEntry(MyMoneySplit::Frozen);
}
void createStatusEntry(MyMoneySplit::reconcileFlagE status);
void updateWidgetState();
bool checkForValidTransaction(bool doUserInteraction = true);
bool isDatePostOpeningDate(const QDate& date, const QString& accountId);
bool postdateChanged(const QDate& date);
bool costCenterChanged(int costCenterIndex);
bool categoryChanged(const QString& accountId);
bool numberChanged(const QString& newNumber);
bool valueChanged(CreditDebitHelper* valueHelper);
Ui_NewTransactionEditor* ui;
AccountNamesFilterProxyModel* accountsModel;
QSortFilterProxyModel* costCenterModel;
QSortFilterProxyModel* payeesModel;
bool accepted;
bool costCenterRequired;
bool costCenterOk;
SplitModel splitModel;
QStandardItemModel statusModel;
QString transactionSplitId;
MyMoneyAccount account;
MyMoneyTransaction transaction;
MyMoneySplit split;
CreditDebitHelper* amountHelper;
};
void NewTransactionEditor::Private::createStatusEntry(MyMoneySplit::reconcileFlagE status)
{
QStandardItem* p = new QStandardItem(KMyMoneyUtils::reconcileStateToString(status, true));
p->setData(status);
statusModel.appendRow(p);
}
void NewTransactionEditor::Private::updateWidgetState()
{
// just in case it is disabled we turn it on
ui->costCenterCombo->setEnabled(true);
// setup the category/account combo box. If we have a split transaction, we disable the
// combo box altogether. Changes can only be made via the split dialog editor
bool blocked = false;
QModelIndex index;
// update the category combo box
ui->accountCombo->setEnabled(true);
switch(splitModel.rowCount()) {
case 0:
ui->accountCombo->setSelected(QString());
break;
case 1:
index = splitModel.index(0, 0);
ui->accountCombo->setSelected(splitModel.data(index, LedgerRole::AccountIdRole).toString());
break;
default:
index = splitModel.index(0, 0);
blocked = ui->accountCombo->lineEdit()->blockSignals(true);
ui->accountCombo->lineEdit()->setText(i18n("Split transaction"));
ui->accountCombo->setDisabled(true);
ui->accountCombo->lineEdit()->blockSignals(blocked);
ui->costCenterCombo->setDisabled(true);
ui->costCenterLabel->setDisabled(true);
break;
}
ui->accountCombo->hidePopup();
// update the costcenter combo box
if(ui->costCenterCombo->isEnabled()) {
// extract the cost center
index = splitModel.index(0, 0);
QModelIndexList ccList = costCenterModel->match(costCenterModel->index(0, 0), CostCenterModel::CostCenterIdRole,
splitModel.data(index, LedgerRole::CostCenterIdRole),
1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
if (ccList.count() > 0) {
index = ccList.front();
ui->costCenterCombo->setCurrentIndex(index.row());
}
}
}
bool NewTransactionEditor::Private::checkForValidTransaction(bool doUserInteraction)
{
QStringList infos;
bool rc = true;
if(!postdateChanged(ui->dateEdit->date())) {
infos << ui->dateEdit->toolTip();
rc = false;
}
if(!costCenterChanged(ui->costCenterCombo->currentIndex())) {
infos << ui->costCenterCombo->toolTip();
rc = false;
}
if(doUserInteraction) {
/// @todo add dialog here that shows the @a infos about the problem
}
return rc;
}
bool NewTransactionEditor::Private::isDatePostOpeningDate(const QDate& date, const QString& accountId)
{
bool rc = true;
try {
MyMoneyAccount account = MyMoneyFile::instance()->account(accountId);
const bool isIncomeExpense = account.isIncomeExpense();
// we don't check for categories
if(!isIncomeExpense) {
if(date < account.openingDate())
rc = false;
}
} catch (MyMoneyException &e) {
qDebug() << "Ooops: invalid account id" << accountId << "in" << Q_FUNC_INFO;
}
return rc;
}
bool NewTransactionEditor::Private::postdateChanged(const QDate& date)
{
bool rc = true;
WidgetHintFrame::hide(ui->dateEdit, i18n("The posting date of the transaction."));
// collect all account ids
QStringList accountIds;
accountIds << account.id();
for(int row = 0; row < splitModel.rowCount(); ++row) {
QModelIndex index = splitModel.index(row, 0);
accountIds << splitModel.data(index, LedgerRole::AccountIdRole).toString();;
}
Q_FOREACH(QString accountId, accountIds) {
if(!isDatePostOpeningDate(date, accountId)) {
MyMoneyAccount account = MyMoneyFile::instance()->account(accountId);
WidgetHintFrame::show(ui->dateEdit, i18n("The posting date is prior to the opening date of account <b>%1</b>.").arg(account.name()));
rc = false;
break;
}
}
return rc;
}
bool NewTransactionEditor::Private::costCenterChanged(int costCenterIndex)
{
bool rc = true;
WidgetHintFrame::hide(ui->costCenterCombo, i18n("The cost center this transaction should be assigned to."));
if(costCenterIndex != -1) {
if(costCenterRequired && ui->costCenterCombo->currentText().isEmpty()) {
WidgetHintFrame::show(ui->costCenterCombo, i18n("A cost center assignment is required for a transaction in the selected category."));
rc = false;
}
if(rc == true && splitModel.rowCount() == 1) {
QModelIndex index = costCenterModel->index(costCenterIndex, 0);
QString costCenterId = costCenterModel->data(index, CostCenterModel::CostCenterIdRole).toString();
index = splitModel.index(0, 0);
splitModel.setData(index, costCenterId, LedgerRole::CostCenterIdRole);
}
}
return rc;
}
bool NewTransactionEditor::Private::categoryChanged(const QString& accountId)
{
bool rc = true;
if(!accountId.isEmpty() && splitModel.rowCount() <= 1) {
try {
MyMoneyAccount category = MyMoneyFile::instance()->account(accountId);
const bool isIncomeExpense = category.isIncomeExpense();
ui->costCenterCombo->setEnabled(isIncomeExpense);
ui->costCenterLabel->setEnabled(isIncomeExpense);
costCenterRequired = category.isCostCenterRequired();
rc &= costCenterChanged(ui->costCenterCombo->currentIndex());
rc &= postdateChanged(ui->dateEdit->date());
// make sure we have a split in the model
bool newSplit = false;
if(splitModel.rowCount() == 0) {
splitModel.addEmptySplitEntry();
newSplit = true;
}
const QModelIndex index = splitModel.index(0, 0);
splitModel.setData(index, accountId, LedgerRole::AccountIdRole);
if(newSplit) {
costCenterChanged(ui->costCenterCombo->currentIndex());
if(amountHelper->haveValue()) {
splitModel.setData(index, QVariant::fromValue<MyMoneyMoney>(-amountHelper->value()), LedgerRole::SplitValueRole);
/// @todo make sure to convert initial value to shares according to price information
splitModel.setData(index, QVariant::fromValue<MyMoneyMoney>(-amountHelper->value()), LedgerRole::SplitSharesRole);
}
}
/// @todo we need to make sure to support multiple currencies here
} catch (MyMoneyException &e) {
qDebug() << "Ooops: invalid account id" << accountId << "in" << Q_FUNC_INFO;
}
}
return rc;
}
bool NewTransactionEditor::Private::numberChanged(const QString& newNumber)
{
bool rc = true;
WidgetHintFrame::hide(ui->numberEdit, i18n("The check number used for this transaction."));
if(!newNumber.isEmpty()) {
const LedgerModel* model = Models::instance()->ledgerModel();
QModelIndexList list = model->match(model->index(0, 0), LedgerRole::NumberRole,
QVariant(newNumber),
-1, // all splits
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
foreach(QModelIndex index, list) {
if(model->data(index, LedgerRole::AccountIdRole) == account.id()
&& model->data(index, LedgerRole::TransactionSplitIdRole) != transactionSplitId) {
WidgetHintFrame::show(ui->numberEdit, i18n("The check number <b>%1</b> has already been used in this account.").arg(newNumber));
rc = false;
break;
}
}
}
return rc;
}
bool NewTransactionEditor::Private::valueChanged(CreditDebitHelper* valueHelper)
{
bool rc = true;
if(valueHelper->haveValue() && splitModel.rowCount() <= 1) {
rc = false;
try {
MyMoneyMoney shares;
if(splitModel.rowCount() == 1) {
const QModelIndex index = splitModel.index(0, 0);
splitModel.setData(index, QVariant::fromValue<MyMoneyMoney>(-amountHelper->value()), LedgerRole::SplitValueRole);
/// @todo make sure to support multiple currencies
splitModel.setData(index, QVariant::fromValue<MyMoneyMoney>(-amountHelper->value()), LedgerRole::SplitSharesRole);
} else {
/// @todo ask what to do: if the rest of the splits is the same amount we could simply reverse the sign
/// of all splits, otherwise we could ask if the user wants to start the split editor or anything else.
}
rc = true;
} catch (MyMoneyException &e) {
qDebug() << "Ooops: somwthing went wrong in" << Q_FUNC_INFO;
}
}
return rc;
}
NewTransactionEditor::NewTransactionEditor(QWidget* parent, const QString& accountId)
: QFrame(parent, Qt::FramelessWindowHint /* | Qt::X11BypassWindowManagerHint */)
, d(new Private(this))
{
auto const model = Models::instance()->accountsModel();
// extract account information from model
const auto index = model->accountById(accountId);
d->account = model->data(index, (int)eAccountsModel::Role::Account).value<MyMoneyAccount>();
d->ui->setupUi(this);
- d->accountsModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Asset, MyMoneyAccount::Liability, MyMoneyAccount::Income, MyMoneyAccount::Expense, MyMoneyAccount::Equity});
+ d->accountsModel->addAccountGroup(QVector<eMyMoney::Account> {eMyMoney::Account::Asset, eMyMoney::Account::Liability, eMyMoney::Account::Income, eMyMoney::Account::Expense, eMyMoney::Account::Equity});
d->accountsModel->setHideEquityAccounts(false);
d->accountsModel->setSourceModel(model);
d->accountsModel->setSourceColumns(model->getColumns());
d->accountsModel->sort((int)eAccountsModel::Column::Account);
d->ui->accountCombo->setModel(d->accountsModel);
d->costCenterModel->setSortRole(Qt::DisplayRole);
d->costCenterModel->setSourceModel(Models::instance()->costCenterModel());
d->costCenterModel->sort(0);
d->ui->costCenterCombo->setEditable(true);
d->ui->costCenterCombo->setModel(d->costCenterModel);
d->ui->costCenterCombo->setModelColumn(0);
d->ui->costCenterCombo->completer()->setFilterMode(Qt::MatchContains);
d->payeesModel->setSortRole(Qt::DisplayRole);
d->payeesModel->setSourceModel(Models::instance()->payeesModel());
d->payeesModel->sort(0);
d->ui->payeeEdit->setEditable(true);
d->ui->payeeEdit->setModel(d->payeesModel);
d->ui->payeeEdit->setModelColumn(0);
d->ui->payeeEdit->completer()->setFilterMode(Qt::MatchContains);
d->ui->enterButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogOK]));
d->ui->cancelButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogCancel]));
d->ui->statusCombo->setModel(&d->statusModel);
d->ui->dateEdit->setDisplayFormat(QLocale().dateFormat(QLocale::ShortFormat));
d->ui->amountEditCredit->setAllowEmpty(true);
d->ui->amountEditDebit->setAllowEmpty(true);
d->amountHelper = new CreditDebitHelper(this, d->ui->amountEditCredit, d->ui->amountEditDebit);
WidgetHintFrameCollection* frameCollection = new WidgetHintFrameCollection(this);
frameCollection->addFrame(new WidgetHintFrame(d->ui->dateEdit));
frameCollection->addFrame(new WidgetHintFrame(d->ui->costCenterCombo));
frameCollection->addFrame(new WidgetHintFrame(d->ui->numberEdit, WidgetHintFrame::Warning));
frameCollection->addWidget(d->ui->enterButton);
connect(d->ui->numberEdit, SIGNAL(textChanged(QString)), this, SLOT(numberChanged(QString)));
connect(d->ui->costCenterCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(costCenterChanged(int)));
connect(d->ui->accountCombo, SIGNAL(accountSelected(QString)), this, SLOT(categoryChanged(QString)));
connect(d->ui->dateEdit, SIGNAL(dateChanged(QDate)), this, SLOT(postdateChanged(QDate)));
connect(d->amountHelper, SIGNAL(valueChanged()), this, SLOT(valueChanged()));
connect(d->ui->cancelButton, SIGNAL(clicked(bool)), this, SLOT(reject()));
connect(d->ui->enterButton, SIGNAL(clicked(bool)), this, SLOT(acceptEdit()));
connect(d->ui->splitEditorButton, SIGNAL(clicked(bool)), this, SLOT(editSplits()));
// setup tooltip
// setWindowFlags(Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
}
NewTransactionEditor::~NewTransactionEditor()
{
}
bool NewTransactionEditor::accepted() const
{
return d->accepted;
}
void NewTransactionEditor::acceptEdit()
{
if(d->checkForValidTransaction()) {
d->accepted = true;
emit done();
}
}
void NewTransactionEditor::reject()
{
emit done();
}
void NewTransactionEditor::keyPressEvent(QKeyEvent* e)
{
if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) {
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
{
if(focusWidget() == d->ui->cancelButton) {
reject();
} else {
if(d->ui->enterButton->isEnabled()) {
d->ui->enterButton->click();
}
return;
}
}
break;
case Qt::Key_Escape:
reject();
break;
default:
e->ignore();
return;
}
} else {
e->ignore();
}
}
void NewTransactionEditor::loadTransaction(const QString& id)
{
const LedgerModel* model = Models::instance()->ledgerModel();
const QString transactionId = model->transactionIdFromTransactionSplitId(id);
if(id.isEmpty()) {
d->transactionSplitId.clear();
d->transaction = MyMoneyTransaction();
if(lastUsedPostDate()->isValid()) {
d->ui->dateEdit->setDate(*lastUsedPostDate());
} else {
d->ui->dateEdit->setDate(QDate::currentDate());
}
bool blocked = d->ui->accountCombo->lineEdit()->blockSignals(true);
d->ui->accountCombo->lineEdit()->clear();
d->ui->accountCombo->lineEdit()->blockSignals(blocked);
} else {
// find which item has this id and set is as the current item
QModelIndexList list = model->match(model->index(0, 0), LedgerRole::TransactionIdRole,
QVariant(transactionId),
-1, // all splits
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
Q_FOREACH(QModelIndex index, list) {
// the selected split?
const QString transactionSplitId = model->data(index, LedgerRole::TransactionSplitIdRole).toString();
if(transactionSplitId == id) {
d->transactionSplitId = id;
d->transaction = model->data(index, LedgerRole::TransactionRole).value<MyMoneyTransaction>();
d->split = model->data(index, LedgerRole::SplitRole).value<MyMoneySplit>();
d->ui->dateEdit->setDate(model->data(index, LedgerRole::PostDateRole).toDate());
d->ui->payeeEdit->lineEdit()->setText(model->data(index, LedgerRole::PayeeNameRole).toString());
d->ui->memoEdit->clear();
d->ui->memoEdit->insertPlainText(model->data(index, LedgerRole::MemoRole).toString());
d->ui->memoEdit->moveCursor(QTextCursor::Start);
d->ui->memoEdit->ensureCursorVisible();
// The calculator for the amount field can simply be added as an icon to the line edit widget.
// See http://stackoverflow.com/questions/11381865/how-to-make-an-extra-icon-in-qlineedit-like-this howto do it
d->ui->amountEditCredit->setText(model->data(model->index(index.row(), LedgerModel::PaymentColumn)).toString());
d->ui->amountEditDebit->setText(model->data(model->index(index.row(), LedgerModel::DepositColumn)).toString());
d->ui->numberEdit->setText(model->data(index, LedgerRole::NumberRole).toString());
d->ui->statusCombo->setCurrentIndex(model->data(index, LedgerRole::NumberRole).toInt());
QModelIndexList stList = d->statusModel.match(d->statusModel.index(0, 0), Qt::UserRole+1, model->data(index, LedgerRole::ReconciliationRole).toInt());
if(stList.count()) {
QModelIndex stIndex = stList.front();
d->ui->statusCombo->setCurrentIndex(stIndex.row());
}
} else {
d->splitModel.addSplit(transactionSplitId);
}
}
d->updateWidgetState();
}
// set focus to payee edit once we return to event loop
QMetaObject::invokeMethod(d->ui->payeeEdit, "setFocus", Qt::QueuedConnection);
}
void NewTransactionEditor::numberChanged(const QString& newNumber)
{
d->numberChanged(newNumber);
}
void NewTransactionEditor::categoryChanged(const QString& accountId)
{
d->categoryChanged(accountId);
}
void NewTransactionEditor::costCenterChanged(int costCenterIndex)
{
d->costCenterChanged(costCenterIndex);
}
void NewTransactionEditor::postdateChanged(const QDate& date)
{
d->postdateChanged(date);
}
void NewTransactionEditor::valueChanged()
{
d->valueChanged(d->amountHelper);
}
void NewTransactionEditor::editSplits()
{
SplitModel splitModel;
splitModel.deepCopy(d->splitModel, true);
// create an empty split at the end
splitModel.addEmptySplitEntry();
QPointer<SplitDialog> splitDialog = new SplitDialog(d->account, transactionAmount(), this);
splitDialog->setModel(&splitModel);
int rc = splitDialog->exec();
if(splitDialog && (rc == QDialog::Accepted)) {
// remove that empty split again before we update the splits
splitModel.removeEmptySplitEntry();
// copy the splits model contents
d->splitModel.deepCopy(splitModel, true);
// update the transaction amount
d->amountHelper->setValue(splitDialog->transactionAmount());
d->updateWidgetState();
QWidget *next = d->ui->tagComboBox;
if(d->ui->costCenterCombo->isEnabled()) {
next = d->ui->costCenterCombo;
}
next->setFocus();
}
if(splitDialog) {
splitDialog->deleteLater();
}
}
MyMoneyMoney NewTransactionEditor::transactionAmount() const
{
return d->amountHelper->value();
}
void NewTransactionEditor::saveTransaction()
{
MyMoneyTransaction t;
if(!d->transactionSplitId.isEmpty()) {
t = d->transaction;
} else {
// we keep the date when adding a new transaction
// for the next new one
*lastUsedPostDate() = d->ui->dateEdit->date();
}
QList<MyMoneySplit> splits = t.splits();
// first remove the splits that are gone
QList<MyMoneySplit>::iterator it;
for(it = splits.begin(); it != splits.end(); ++it) {
if((*it).id() == d->split.id()) {
continue;
}
int row;
for(row = 0; row < d->splitModel.rowCount(); ++row) {
QModelIndex index = d->splitModel.index(row, 0);
if(d->splitModel.data(index, LedgerRole::SplitIdRole).toString() == (*it).id()) {
break;
}
}
// if the split is not in the model, we get rid of it
if(d->splitModel.rowCount() == row) {
t.removeSplit(*it);
}
}
MyMoneyFileTransaction ft;
try {
// new we update the split we are opened for
MyMoneySplit sp(d->split);
sp.setNumber(d->ui->numberEdit->text());
sp.setMemo(d->ui->memoEdit->toPlainText());
sp.setShares(d->amountHelper->value());
if(t.commodity().isEmpty()) {
t.setCommodity(d->account.currencyId());
sp.setValue(d->amountHelper->value());
} else {
/// @todo check that the transactions commodity is the same
/// as the one of the account this split references. If
/// that is not the case, the next statement would create
/// a problem
sp.setValue(d->amountHelper->value());
}
if(sp.reconcileFlag() != MyMoneySplit::Reconciled
&& !sp.reconcileDate().isValid()
&& d->ui->statusCombo->currentIndex() == MyMoneySplit::Reconciled) {
sp.setReconcileDate(QDate::currentDate());
}
sp.setReconcileFlag(static_cast<MyMoneySplit::reconcileFlagE>(d->ui->statusCombo->currentIndex()));
// sp.setPayeeId(d->ui->payeeEdit->cu)
if(sp.id().isEmpty()) {
t.addSplit(sp);
} else {
t.modifySplit(sp);
}
t.setPostDate(d->ui->dateEdit->date());
// now update and add what we have in the model
const SplitModel * model = &d->splitModel;
for(int row = 0; row < model->rowCount(); ++row) {
QModelIndex index = model->index(row, 0);
MyMoneySplit s;
const QString splitId = model->data(index, LedgerRole::SplitIdRole).toString();
if(!SplitModel::isNewSplitId(splitId)) {
s = t.splitById(splitId);
}
s.setNumber(model->data(index, LedgerRole::NumberRole).toString());
s.setMemo(model->data(index, LedgerRole::MemoRole).toString());
s.setAccountId(model->data(index, LedgerRole::AccountIdRole).toString());
s.setShares(model->data(index, LedgerRole::SplitSharesRole).value<MyMoneyMoney>());
s.setValue(model->data(index, LedgerRole::SplitValueRole).value<MyMoneyMoney>());
s.setCostCenterId(model->data(index, LedgerRole::CostCenterIdRole).toString());
s.setPayeeId(model->data(index, LedgerRole::PayeeIdRole).toString());
// reconcile flag and date
if(s.id().isEmpty()) {
t.addSplit(s);
} else {
t.modifySplit(s);
}
}
if(t.id().isEmpty()) {
MyMoneyFile::instance()->addTransaction(t);
} else {
MyMoneyFile::instance()->modifyTransaction(t);
}
ft.commit();
} catch (const MyMoneyException &e) {
qDebug() << Q_FUNC_INFO << "something went wrong" << e.what();
}
}
diff --git a/kmymoney/views/simpleledgerview.cpp b/kmymoney/views/simpleledgerview.cpp
index 66d081793..7649492e5 100644
--- a/kmymoney/views/simpleledgerview.cpp
+++ b/kmymoney/views/simpleledgerview.cpp
@@ -1,242 +1,242 @@
/***************************************************************************
simpleledgerview.cpp
-------------------
begin : Sat Aug 8 2015
copyright : (C) 2015 by Thomas Baumgart
email : Thomas Baumgart <tbaumgart@kde.org>
***************************************************************************/
/***************************************************************************
* *
* 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 "simpleledgerview.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QTabBar>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "ledgerviewpage.h"
#include "models.h"
#include "accountsmodel.h"
#include "kmymoneyaccountcombo.h"
#include "ui_simpleledgerview.h"
#include "icons/icons.h"
#include "kmymoneyview.h"
#include "mymoneyfile.h"
using namespace Icons;
class SimpleLedgerView::Private
{
public:
Private(SimpleLedgerView* p)
: parent(p)
, ui(new Ui_SimpleLedgerView)
, accountsModel(new AccountNamesFilterProxyModel(parent))
, newTabWidget(0)
, lastIdx(-1)
, inModelUpdate(false)
, m_needLoad(true)
{}
~Private() {}
SimpleLedgerView* parent;
Ui_SimpleLedgerView* ui;
AccountNamesFilterProxyModel* accountsModel;
QWidget* newTabWidget;
int lastIdx;
bool inModelUpdate;
bool m_needLoad;
};
SimpleLedgerView::SimpleLedgerView(KMyMoneyApp *kmymoney, KMyMoneyView *kmymoneyview)
: QWidget(nullptr)
, d(new Private(this))
, m_kmymoney(kmymoney)
, m_kmymoneyview(kmymoneyview)
{
}
SimpleLedgerView::~SimpleLedgerView()
{
if (!d->m_needLoad)
delete d->ui;
delete d;
}
void SimpleLedgerView::init()
{
d->m_needLoad = false;
d->ui->setupUi(this);
d->ui->ledgerTab->setTabIcon(0, QIcon::fromTheme(g_Icons[Icon::ListAdd]));
d->ui->ledgerTab->setTabText(0, QString());
d->newTabWidget = d->ui->ledgerTab->widget(0);
// remove close button from new page
QTabBar* bar = d->ui->ledgerTab->findChild<QTabBar*>();
if(bar) {
QTabBar::ButtonPosition closeSide = (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, d->newTabWidget);
QWidget *w = bar->tabButton(0, closeSide);
bar->setTabButton(0, closeSide, 0);
w->deleteLater();
connect(bar, SIGNAL(tabMoved(int,int)), this, SLOT(checkTabOrder(int,int)));
}
connect(d->ui->accountCombo, SIGNAL(accountSelected(QString)), this, SLOT(openNewLedger(QString)));
connect(d->ui->ledgerTab, &QTabWidget::currentChanged, this, &SimpleLedgerView::tabSelected);
connect(Models::instance(), &Models::modelsLoaded, this, &SimpleLedgerView::updateModels);
connect(d->ui->ledgerTab, &QTabWidget::tabCloseRequested, this, &SimpleLedgerView::closeLedger);
connect(m_kmymoneyview, &KMyMoneyView::fileClosed, this, &SimpleLedgerView::closeLedgers);
connect(m_kmymoneyview, &KMyMoneyView::fileOpened, this, &SimpleLedgerView::openFavoriteLedgers);
- d->accountsModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Asset, MyMoneyAccount::Liability, MyMoneyAccount::Equity});
+ d->accountsModel->addAccountGroup(QVector<eMyMoney::Account> {eMyMoney::Account::Asset, eMyMoney::Account::Liability, eMyMoney::Account::Equity});
d->accountsModel->setHideEquityAccounts(false);
auto const model = Models::instance()->accountsModel();
d->accountsModel->setSourceModel(model);
d->accountsModel->setSourceColumns(model->getColumns());
d->accountsModel->sort((int)eAccountsModel::Column::Account);
d->ui->accountCombo->setModel(d->accountsModel);
tabSelected(0);
updateModels();
}
void SimpleLedgerView::openNewLedger(QString accountId)
{
if(d->inModelUpdate || accountId.isEmpty())
return;
LedgerViewPage* view = 0;
// check if ledger is already opened
for(int idx=0; idx < d->ui->ledgerTab->count()-1; ++idx) {
view = qobject_cast<LedgerViewPage*>(d->ui->ledgerTab->widget(idx));
if(view) {
if(accountId == view->accountId()) {
d->ui->ledgerTab->setCurrentIndex(idx);
return;
}
}
}
// need a new tab, we insert it before the rightmost one
QModelIndex index = Models::instance()->accountsModel()->accountById(accountId);
if(index.isValid()) {
// create new ledger view page
MyMoneyAccount acc = Models::instance()->accountsModel()->data(index, (int)eAccountsModel::Role::Account).value<MyMoneyAccount>();
view = new LedgerViewPage(this);
view->setAccount(acc);
view->setShowEntryForNewTransaction();
/// @todo setup current global setting for form visibility
// view->showTransactionForm(...);
// insert new ledger view page in tab view
int newIdx = d->ui->ledgerTab->insertTab(d->ui->ledgerTab->count()-1, view, acc.name());
d->ui->ledgerTab->setCurrentIndex(d->ui->ledgerTab->count()-1);
d->ui->ledgerTab->setCurrentIndex(newIdx);
}
}
void SimpleLedgerView::tabSelected(int idx)
{
// qDebug() << "tabSelected" << idx << (d->ui->ledgerTab->count()-1);
if(idx != (d->ui->ledgerTab->count()-1)) {
d->lastIdx = idx;
}
}
void SimpleLedgerView::updateModels()
{
d->inModelUpdate = true;
// d->ui->accountCombo->
d->ui->accountCombo->expandAll();
d->ui->accountCombo->setSelected(MyMoneyFile::instance()->asset().id());
d->inModelUpdate = false;
}
void SimpleLedgerView::closeLedger(int idx)
{
// don't react on the close request for the new ledger function
if(idx != (d->ui->ledgerTab->count()-1)) {
d->ui->ledgerTab->removeTab(idx);
}
}
void SimpleLedgerView::checkTabOrder(int from, int to)
{
if(d->inModelUpdate)
return;
QTabBar* bar = d->ui->ledgerTab->findChild<QTabBar*>();
if(bar) {
const int rightMostIdx = d->ui->ledgerTab->count()-1;
if(from == rightMostIdx) {
// someone tries to move the new account tab away from the rightmost position
d->inModelUpdate = true;
bar->moveTab(to, from);
d->inModelUpdate = false;
}
}
}
void SimpleLedgerView::showTransactionForm(bool show)
{
emit showForms(show);
}
void SimpleLedgerView::closeLedgers()
{
int tabCount = d->ui->ledgerTab->count();
// check that we have a least one tab that can be closed
if(tabCount > 1) {
// we keep the tab with the selector open at all times
// which is located in the right most position
--tabCount;
do {
--tabCount;
closeLedger(tabCount);
} while(tabCount > 0);
}
}
void SimpleLedgerView::openFavoriteLedgers()
{
AccountsModel* model = Models::instance()->accountsModel();
QModelIndex start = model->index(0, 0);
QModelIndexList indexes = model->match(start, (int)eAccountsModel::Role::Favorite, QVariant(true), -1, Qt::MatchRecursive);
// indexes now has a list of favorite accounts but two entries for each.
// that doesn't matter here, since openNewLedger() can handle duplicates
Q_FOREACH(QModelIndex index, indexes) {
openNewLedger(model->data(index, (int)eAccountsModel::Role::ID).toString());
}
d->ui->ledgerTab->setCurrentIndex(0);
}
void SimpleLedgerView::showEvent(QShowEvent* event)
{
if (d->m_needLoad)
init();
// don't forget base class implementation
QWidget::showEvent(event);
}
diff --git a/kmymoney/widgets/budgetviewproxymodel.cpp b/kmymoney/widgets/budgetviewproxymodel.cpp
index 560059da7..738206969 100644
--- a/kmymoney/widgets/budgetviewproxymodel.cpp
+++ b/kmymoney/widgets/budgetviewproxymodel.cpp
@@ -1,219 +1,220 @@
/***************************************************************************
budgetviewproxymodel.cpp
-------------------
Copyright (C) 2006 by Darren Gould <darren_gould@gmx.de>
Copyright (C) 2006 by Alvaro Soliverez <asoliverez@gmail.com>
Copyright (C) 2017 by Łukasz Wojniłowicz <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 "budgetviewproxymodel.h"
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyutils.h"
#include "mymoneyfile.h"
+#include "mymoneysecurity.h"
#include "models.h"
#include "accountsmodel.h"
#include "modelenums.h"
using namespace eAccountsModel;
BudgetViewProxyModel::BudgetViewProxyModel(QObject *parent) :
AccountsViewProxyModel(parent)
{
}
/**
* This function was reimplemented to add the data needed by the other columns that this model
* is adding besides the columns of the @ref AccountsModel.
*/
QVariant BudgetViewProxyModel::data(const QModelIndex &index, int role) const
{
if (!MyMoneyFile::instance()->storageAttached())
return QVariant();
const auto sourceColumn = m_mdlColumns->at(mapToSource(index).column());
static QVector<Column> columnsToProcess {Column::TotalBalance, Column::TotalValue/*, AccountsModel::PostedValue*/, Column::Account};
if (columnsToProcess.contains(sourceColumn)) {
const auto ixAccount = mapToSource(BudgetViewProxyModel::index(index.row(), static_cast<int>(Column::Account), index.parent()));
const auto account = ixAccount.data((int)Role::Account).value<MyMoneyAccount>();
auto const file = MyMoneyFile::instance();
switch (role) {
case Qt::DisplayRole:
{
switch (sourceColumn) {
case Column::TotalBalance:
if (file->security(account.currencyId()) != file->baseCurrency())
return QVariant(MyMoneyUtils::formatMoney(accountBalance(account.id()), file->security(account.currencyId())));
else
return QVariant();
case Column::TotalValue:
return QVariant(MyMoneyUtils::formatMoney(computeTotalValue(ixAccount), file->baseCurrency()));
// FIXME: Posted value doesn't correspond with total value without below code. Investigate why and wheather it matters.
// case AccountsModel::PostedValue:
// return QVariant(MyMoneyUtils::formatMoney(accountValue(account, accountBalance(account.id())), file->baseCurrency()));
default:
break;
}
}
case (int)Role::Balance:
if (file->security(account.currencyId()) != file->baseCurrency())
return QVariant::fromValue(accountBalance(account.id()));
else
return QVariant();
case (int)Role::TotalValue:
return QVariant::fromValue(computeTotalValue(ixAccount));
case (int)Role::Value:
return QVariant::fromValue(accountValue(account, accountBalance(account.id())));
default:
break;
}
}
return AccountsViewProxyModel::data(index, role);
}
Qt::ItemFlags BudgetViewProxyModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = AccountsViewProxyModel::flags(index);
if (!index.parent().isValid())
return flags & ~Qt::ItemIsSelectable;
// check if any of the parent accounts has the 'include subaccounts'
// flag set. If so, we don't allow selecting this account
QModelIndex idx = index.parent();
while (idx.isValid()) {
QModelIndex source_idx = mapToSource(idx);
QVariant accountData = sourceModel()->data(source_idx, (int)Role::Account);
if (accountData.canConvert<MyMoneyAccount>()) {
MyMoneyAccount account = accountData.value<MyMoneyAccount>();
// find out if the account is budgeted
MyMoneyBudget::AccountGroup budgetAccount = m_budget.account(account.id());
if (budgetAccount.id() == account.id()) {
if (budgetAccount.budgetSubaccounts()) {
return flags & ~Qt::ItemIsEnabled;
}
}
}
idx = idx.parent();
}
return flags;
}
void BudgetViewProxyModel::setBudget(const MyMoneyBudget& budget)
{
m_budget = budget;
invalidate();
checkBalance();
}
bool BudgetViewProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (hideUnusedIncomeExpenseAccounts()) {
const auto index = sourceModel()->index(source_row, static_cast<int>(Column::Account), source_parent);
const auto accountData = sourceModel()->data(index, (int)Role::Account);
if (accountData.canConvert<MyMoneyAccount>()) {
const auto account = accountData.value<MyMoneyAccount>();
MyMoneyMoney balance;
// find out if the account is budgeted
const auto budgetAccount = m_budget.account(account.id());
if (budgetAccount.id() == account.id()) {
balance = budgetAccount.balance();
switch (budgetAccount.budgetLevel()) {
case MyMoneyBudget::AccountGroup::eMonthly:
balance *= MyMoneyMoney(12);
break;
default:
break;
}
}
if (!balance.isZero())
return AccountsProxyModel::filterAcceptsRow(source_row, source_parent);
}
for (auto i = 0; i < sourceModel()->rowCount(index); ++i) {
if (filterAcceptsRow(i, index))
return AccountsProxyModel::filterAcceptsRow(i, index);
}
return false;
}
return AccountsProxyModel::filterAcceptsRow(source_row, source_parent);
}
MyMoneyMoney BudgetViewProxyModel::accountBalance(const QString &accountId) const
{
MyMoneyMoney balance;
// find out if the account is budgeted
MyMoneyBudget::AccountGroup budgetAccount = m_budget.account(accountId);
if (budgetAccount.id() == accountId) {
balance = budgetAccount.balance();
switch (budgetAccount.budgetLevel()) {
case MyMoneyBudget::AccountGroup::eMonthly:
balance *= MyMoneyMoney(12);
break;
default:
break;
}
}
return balance;
}
MyMoneyMoney BudgetViewProxyModel::accountValue(const MyMoneyAccount &account, const MyMoneyMoney &balance) const
{
return Models::instance()->accountsModel()->accountValue(account, balance);
}
MyMoneyMoney BudgetViewProxyModel::computeTotalValue(const QModelIndex &source_index) const
{
auto model = sourceModel();
auto account = model->data(source_index, (int)Role::Account).value<MyMoneyAccount>();
auto totalValue = accountValue(account, accountBalance(account.id()));
for (auto i = 0; i < model->rowCount(source_index); ++i)
totalValue += computeTotalValue(model->index(i, static_cast<int>(Column::Account), source_index));
return totalValue;
}
void BudgetViewProxyModel::checkBalance()
{
// compute the balance
QModelIndexList incomeList = match(index(0, 0),
(int)Role::ID,
MyMoneyFile::instance()->income().id(),
1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
QModelIndexList expenseList = match(index(0, 0),
(int)Role::ID,
MyMoneyFile::instance()->expense().id(),
1,
Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
MyMoneyMoney balance;
if (!incomeList.isEmpty() && !expenseList.isEmpty()) {
QVariant incomeValue = data(incomeList.front(), (int)Role::TotalValue);
QVariant expenseValue = data(expenseList.front(), (int)Role::TotalValue);
if (incomeValue.isValid() && expenseValue.isValid()) {
balance = incomeValue.value<MyMoneyMoney>() - expenseValue.value<MyMoneyMoney>();
}
}
if (m_lastBalance != balance) {
m_lastBalance = balance;
emit balanceChanged(m_lastBalance);
}
}
diff --git a/kmymoney/widgets/kmymoneyaccountcompletion.cpp b/kmymoney/widgets/kmymoneyaccountcompletion.cpp
index e14310d11..079213ade 100644
--- a/kmymoney/widgets/kmymoneyaccountcompletion.cpp
+++ b/kmymoney/widgets/kmymoneyaccountcompletion.cpp
@@ -1,106 +1,106 @@
/***************************************************************************
kmymoneyaccountcompletion.cpp - description
-------------------
begin : Mon Apr 26 2004
copyright : (C) 2000-2004 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 <config-kmymoney.h>
#include "kmymoneyaccountcompletion.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QRegExp>
#include <QLayout>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
kMyMoneyAccountCompletion::kMyMoneyAccountCompletion(QWidget *parent) :
kMyMoneyCompletion(parent)
{
delete m_selector;
m_selector = new kMyMoneyAccountSelector(this, 0, false);
m_selector->listView()->setFocusProxy(parent);
layout()->addWidget(m_selector);
#ifndef KMM_DESIGNER
// Default is to show all accounts
// FIXME We should leave this also to the caller
AccountSet set;
- set.addAccountGroup(MyMoneyAccount::Asset);
- set.addAccountGroup(MyMoneyAccount::Liability);
- set.addAccountGroup(MyMoneyAccount::Income);
- set.addAccountGroup(MyMoneyAccount::Expense);
+ set.addAccountGroup(eMyMoney::Account::Asset);
+ set.addAccountGroup(eMyMoney::Account::Liability);
+ set.addAccountGroup(eMyMoney::Account::Income);
+ set.addAccountGroup(eMyMoney::Account::Expense);
set.load(selector());
#endif
connectSignals(m_selector, m_selector->listView());
}
kMyMoneyAccountCompletion::~kMyMoneyAccountCompletion()
{
}
void kMyMoneyAccountCompletion::slotMakeCompletion(const QString& txt)
{
// if(txt.isEmpty() || txt.length() == 0)
// return;
int cnt = 0;
if (txt.contains(MyMoneyFile::AccountSeperator) == 0) {
m_lastCompletion = QRegExp(QRegExp::escape(txt), Qt::CaseInsensitive);
cnt = selector()->slotMakeCompletion(txt);
} else {
QStringList parts = txt.split(MyMoneyFile::AccountSeperator, QString::SkipEmptyParts);
QString pattern("^");
QStringList::iterator it;
for (it = parts.begin(); it != parts.end(); ++it) {
if (pattern.length() > 1)
pattern += MyMoneyFile::AccountSeperator;
pattern += QRegExp::escape(QString(*it).trimmed()) + ".*";
}
pattern += '$';
m_lastCompletion = QRegExp(pattern, Qt::CaseInsensitive);
cnt = selector()->slotMakeCompletion(m_lastCompletion);
// if we don't have a match, we try it again, but this time
// we add a wildcard for the top level
if (cnt == 0) {
pattern = pattern.insert(1, QString(".*") + MyMoneyFile::AccountSeperator);
m_lastCompletion = QRegExp(pattern, Qt::CaseInsensitive);
cnt = selector()->slotMakeCompletion(m_lastCompletion);
}
}
if (m_parent && m_parent->isVisible() && !isVisible() && cnt)
show(false);
else {
if (cnt != 0) {
adjustSize();
} else {
hide();
}
}
}
diff --git a/kmymoney/widgets/kmymoneyaccountcompletion.h b/kmymoney/widgets/kmymoneyaccountcompletion.h
index 28539bcd6..0111affc8 100644
--- a/kmymoney/widgets/kmymoneyaccountcompletion.h
+++ b/kmymoney/widgets/kmymoneyaccountcompletion.h
@@ -1,66 +1,66 @@
/***************************************************************************
kmymoneyaccountcompletion.h - description
-------------------
begin : Mon Apr 26 2004
copyright : (C) 2000-2004 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 KMYMONEYACCOUNTCOMPLETION_H
#define KMYMONEYACCOUNTCOMPLETION_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneyaccountselector.h"
#include "kmymoneycompletion.h"
/**
* @author Thomas Baumgart
*/
class kMyMoneyAccountCompletion : public kMyMoneyCompletion
{
Q_OBJECT
public:
kMyMoneyAccountCompletion(QWidget *parent = 0);
virtual ~kMyMoneyAccountCompletion();
- QStringList accountList(const QList<MyMoneyAccount::accountTypeE>& list = QList<MyMoneyAccount::accountTypeE>()) const {
+ QStringList accountList(const QList<eMyMoney::Account>& list = QList<eMyMoney::Account>()) const {
return selector()->accountList(list);
}
/**
* reimplemented from kMyMoneyCompletion
*/
kMyMoneyAccountSelector* selector() const {
return dynamic_cast<kMyMoneyAccountSelector*>(m_selector);
}
public slots:
void slotMakeCompletion(const QString& txt);
};
#endif
diff --git a/kmymoney/widgets/kmymoneyaccountselector.cpp b/kmymoney/widgets/kmymoneyaccountselector.cpp
index c927fec06..20e9f3067 100644
--- a/kmymoney/widgets/kmymoneyaccountselector.cpp
+++ b/kmymoney/widgets/kmymoneyaccountselector.cpp
@@ -1,483 +1,485 @@
/***************************************************************************
kmymoneyaccountselector.cpp - description
-------------------
begin : Thu Sep 18 2003
copyright : (C) 2003 by Thomas Baumgart
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "kmymoneyaccountselector.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
#include <QVBoxLayout>
#include <QPixmapCache>
#include <QPushButton>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
#include "kmymoneyutils.h"
#include "kmymoneyglobalsettings.h"
#include "icons/icons.h"
+#include "mymoneyenums.h"
using namespace Icons;
+using namespace eMyMoney;
kMyMoneyAccountSelector::kMyMoneyAccountSelector(QWidget *parent, Qt::WindowFlags flags, const bool createButtons) :
KMyMoneySelector(parent, flags),
m_allAccountsButton(0),
m_noAccountButton(0),
m_incomeCategoriesButton(0),
m_expenseCategoriesButton(0)
{
if (createButtons) {
QVBoxLayout* buttonLayout = new QVBoxLayout();
buttonLayout->setSpacing(6);
m_allAccountsButton = new QPushButton(this);
m_allAccountsButton->setObjectName("m_allAccountsButton");
m_allAccountsButton->setText(i18nc("Select all accounts", "All"));
buttonLayout->addWidget(m_allAccountsButton);
m_incomeCategoriesButton = new QPushButton(this);
m_incomeCategoriesButton->setObjectName("m_incomeCategoriesButton");
m_incomeCategoriesButton->setText(i18n("Income"));
buttonLayout->addWidget(m_incomeCategoriesButton);
m_expenseCategoriesButton = new QPushButton(this);
m_expenseCategoriesButton->setObjectName("m_expenseCategoriesButton");
m_expenseCategoriesButton->setText(i18n("Expense"));
buttonLayout->addWidget(m_expenseCategoriesButton);
m_noAccountButton = new QPushButton(this);
m_noAccountButton->setObjectName("m_noAccountButton");
m_noAccountButton->setText(i18nc("No account", "None"));
buttonLayout->addWidget(m_noAccountButton);
QSpacerItem* spacer = new QSpacerItem(0, 67, QSizePolicy::Minimum, QSizePolicy::Expanding);
buttonLayout->addItem(spacer);
m_layout->addLayout(buttonLayout);
connect(m_allAccountsButton, SIGNAL(clicked()), this, SLOT(slotSelectAllAccounts()));
connect(m_noAccountButton, SIGNAL(clicked()), this, SLOT(slotDeselectAllAccounts()));
connect(m_incomeCategoriesButton, SIGNAL(clicked()), this, SLOT(slotSelectIncomeCategories()));
connect(m_expenseCategoriesButton, SIGNAL(clicked()), this, SLOT(slotSelectExpenseCategories()));
}
}
kMyMoneyAccountSelector::~kMyMoneyAccountSelector()
{
}
void kMyMoneyAccountSelector::removeButtons()
{
delete m_allAccountsButton;
delete m_incomeCategoriesButton;
delete m_expenseCategoriesButton;
delete m_noAccountButton;
}
void kMyMoneyAccountSelector::selectCategories(const bool income, const bool expense)
{
QTreeWidgetItemIterator it_v(m_treeWidget);
for (; *it_v != 0; ++it_v) {
if ((*it_v)->text(0) == i18n("Income categories"))
selectAllSubItems(*it_v, income);
else if ((*it_v)->text(0) == i18n("Expense categories"))
selectAllSubItems(*it_v, expense);
}
emit stateChanged();
}
void kMyMoneyAccountSelector::setSelectionMode(QTreeWidget::SelectionMode mode)
{
m_incomeCategoriesButton->setHidden(mode == QTreeWidget::MultiSelection);
m_expenseCategoriesButton->setHidden(mode == QTreeWidget::MultiSelection);
KMyMoneySelector::setSelectionMode(mode);
}
-QStringList kMyMoneyAccountSelector::accountList(const QList<MyMoneyAccount::accountTypeE>& filterList) const
+QStringList kMyMoneyAccountSelector::accountList(const QList<Account>& filterList) const
{
QStringList list;
QTreeWidgetItemIterator it(m_treeWidget, QTreeWidgetItemIterator::Selectable);
while (*it) {
QVariant id = (*it)->data(0, KMyMoneySelector::IdRole);
MyMoneyAccount acc = MyMoneyFile::instance()->account(id.toString());
if (filterList.count() == 0 || filterList.contains(acc.accountType()))
list << id.toString();
it++;
}
return list;
}
bool kMyMoneyAccountSelector::match(const QRegExp& exp, QTreeWidgetItem* item) const
{
if (!item->flags().testFlag(Qt::ItemIsSelectable))
return false;
return exp.indexIn(item->data(0, KMyMoneySelector::KeyRole).toString().mid(1)) != -1;
}
bool kMyMoneyAccountSelector::contains(const QString& txt) const
{
QTreeWidgetItemIterator it(m_treeWidget, QTreeWidgetItemIterator::Selectable);
QTreeWidgetItem* it_v;
QString baseName = i18n("Asset") + '|' +
i18n("Liability") + '|' +
i18n("Income") + '|' +
i18n("Expense") + '|' +
i18n("Equity") + '|' +
i18n("Security");
while ((it_v = *it) != 0) {
QRegExp exp(QString("^(?:%1):%2$").arg(baseName).arg(QRegExp::escape(txt)));
if (exp.indexIn(it_v->data(0, KMyMoneySelector::KeyRole).toString().mid(1)) != -1) {
return true;
}
it++;
}
return false;
}
AccountSet::AccountSet() :
m_count(0),
m_file(MyMoneyFile::instance()),
m_favorites(0),
m_hideClosedAccounts(true)
{
}
-void AccountSet::addAccountGroup(MyMoneyAccount::accountTypeE group)
+void AccountSet::addAccountGroup(Account group)
{
- if (group == MyMoneyAccount::Asset) {
- m_typeList << MyMoneyAccount::Checkings;
- m_typeList << MyMoneyAccount::Savings;
- m_typeList << MyMoneyAccount::Cash;
- m_typeList << MyMoneyAccount::AssetLoan;
- m_typeList << MyMoneyAccount::CertificateDep;
- m_typeList << MyMoneyAccount::Investment;
- m_typeList << MyMoneyAccount::Stock;
- m_typeList << MyMoneyAccount::MoneyMarket;
- m_typeList << MyMoneyAccount::Asset;
- m_typeList << MyMoneyAccount::Currency;
-
- } else if (group == MyMoneyAccount::Liability) {
- m_typeList << MyMoneyAccount::CreditCard;
- m_typeList << MyMoneyAccount::Loan;
- m_typeList << MyMoneyAccount::Liability;
-
- } else if (group == MyMoneyAccount::Income) {
- m_typeList << MyMoneyAccount::Income;
-
- } else if (group == MyMoneyAccount::Expense) {
- m_typeList << MyMoneyAccount::Expense;
-
- } else if (group == MyMoneyAccount::Equity) {
- m_typeList << MyMoneyAccount::Equity;
+ if (group == Account::Asset) {
+ m_typeList << Account::Checkings;
+ m_typeList << Account::Savings;
+ m_typeList << Account::Cash;
+ m_typeList << Account::AssetLoan;
+ m_typeList << Account::CertificateDep;
+ m_typeList << Account::Investment;
+ m_typeList << Account::Stock;
+ m_typeList << Account::MoneyMarket;
+ m_typeList << Account::Asset;
+ m_typeList << Account::Currency;
+
+ } else if (group == Account::Liability) {
+ m_typeList << Account::CreditCard;
+ m_typeList << Account::Loan;
+ m_typeList << Account::Liability;
+
+ } else if (group == Account::Income) {
+ m_typeList << Account::Income;
+
+ } else if (group == Account::Expense) {
+ m_typeList << Account::Expense;
+
+ } else if (group == Account::Equity) {
+ m_typeList << Account::Equity;
}
}
-void AccountSet::addAccountType(MyMoneyAccount::accountTypeE type)
+void AccountSet::addAccountType(Account type)
{
m_typeList << type;
}
-void AccountSet::removeAccountType(MyMoneyAccount::accountTypeE type)
+void AccountSet::removeAccountType(Account type)
{
int index = m_typeList.indexOf(type);
if (index != -1) {
m_typeList.removeAt(index);
}
}
void AccountSet::clear()
{
m_typeList.clear();
}
int AccountSet::load(kMyMoneyAccountSelector* selector)
{
QStringList list;
QStringList::ConstIterator it_l;
int count = 0;
int typeMask = 0;
QString currentId;
if (selector->selectionMode() == QTreeWidget::SingleSelection) {
QStringList list;
selector->selectedItems(list);
if (!list.isEmpty())
currentId = list.first();
}
- if (m_typeList.contains(MyMoneyAccount::Checkings)
- || m_typeList.contains(MyMoneyAccount::Savings)
- || m_typeList.contains(MyMoneyAccount::Cash)
- || m_typeList.contains(MyMoneyAccount::AssetLoan)
- || m_typeList.contains(MyMoneyAccount::CertificateDep)
- || m_typeList.contains(MyMoneyAccount::Investment)
- || m_typeList.contains(MyMoneyAccount::Stock)
- || m_typeList.contains(MyMoneyAccount::MoneyMarket)
- || m_typeList.contains(MyMoneyAccount::Asset)
- || m_typeList.contains(MyMoneyAccount::Currency))
+ if (m_typeList.contains(Account::Checkings)
+ || m_typeList.contains(Account::Savings)
+ || m_typeList.contains(Account::Cash)
+ || m_typeList.contains(Account::AssetLoan)
+ || m_typeList.contains(Account::CertificateDep)
+ || m_typeList.contains(Account::Investment)
+ || m_typeList.contains(Account::Stock)
+ || m_typeList.contains(Account::MoneyMarket)
+ || m_typeList.contains(Account::Asset)
+ || m_typeList.contains(Account::Currency))
typeMask |= KMyMoneyUtils::asset;
- if (m_typeList.contains(MyMoneyAccount::CreditCard)
- || m_typeList.contains(MyMoneyAccount::Loan)
- || m_typeList.contains(MyMoneyAccount::Liability))
+ if (m_typeList.contains(Account::CreditCard)
+ || m_typeList.contains(Account::Loan)
+ || m_typeList.contains(Account::Liability))
typeMask |= KMyMoneyUtils::liability;
- if (m_typeList.contains(MyMoneyAccount::Income))
+ if (m_typeList.contains(Account::Income))
typeMask |= KMyMoneyUtils::income;
- if (m_typeList.contains(MyMoneyAccount::Expense))
+ if (m_typeList.contains(Account::Expense))
typeMask |= KMyMoneyUtils::expense;
- if (m_typeList.contains(MyMoneyAccount::Equity))
+ if (m_typeList.contains(Account::Equity))
typeMask |= KMyMoneyUtils::equity;
selector->clear();
QTreeWidget* lv = selector->listView();
m_count = 0;
QString key;
QTreeWidgetItem* after = 0;
// create the favorite section first and sort it to the beginning
key = QString("A%1").arg(i18n("Favorites"));
m_favorites = selector->newItem(i18n("Favorites"), key);
//get the account icon from cache or insert it if it is not there
QPixmap accountPixmap;
if (!QPixmapCache::find("account", accountPixmap)) {
QIcon icon = QIcon::fromTheme(g_Icons[Icon::ViewBankAccount]);
if (!icon.availableSizes().isEmpty())
accountPixmap = icon.pixmap(icon.availableSizes().first());
QPixmapCache::insert("account", accountPixmap);
}
m_favorites->setIcon(0, QIcon(accountPixmap));
for (int mask = 0x01; mask != KMyMoneyUtils::last; mask <<= 1) {
QTreeWidgetItem* item = 0;
if ((typeMask & mask & KMyMoneyUtils::asset) != 0) {
++m_count;
key = QString("B%1").arg(i18n("Asset"));
item = selector->newItem(i18n("Asset accounts"), key);
item->setIcon(0, m_file->asset().accountPixmap());
list = m_file->asset().accountList();
}
if ((typeMask & mask & KMyMoneyUtils::liability) != 0) {
++m_count;
key = QString("C%1").arg(i18n("Liability"));
item = selector->newItem(i18n("Liability accounts"), key);
item->setIcon(0, m_file->liability().accountPixmap());
list = m_file->liability().accountList();
}
if ((typeMask & mask & KMyMoneyUtils::income) != 0) {
++m_count;
key = QString("D%1").arg(i18n("Income"));
item = selector->newItem(i18n("Income categories"), key);
item->setIcon(0, m_file->income().accountPixmap());
list = m_file->income().accountList();
if (selector->selectionMode() == QTreeWidget::MultiSelection) {
selector->m_incomeCategoriesButton->show();
}
}
if ((typeMask & mask & KMyMoneyUtils::expense) != 0) {
++m_count;
key = QString("E%1").arg(i18n("Expense"));
item = selector->newItem(i18n("Expense categories"), key);
item->setIcon(0, m_file->expense().accountPixmap());
list = m_file->expense().accountList();
if (selector->selectionMode() == QTreeWidget::MultiSelection) {
selector->m_expenseCategoriesButton->show();
}
}
if ((typeMask & mask & KMyMoneyUtils::equity) != 0) {
++m_count;
key = QString("F%1").arg(i18n("Equity"));
item = selector->newItem(i18n("Equity accounts"), key);
item->setIcon(0, m_file->equity().accountPixmap());
list = m_file->equity().accountList();
}
if (!after)
after = item;
if (item != 0) {
// scan all matching accounts found in the engine
for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) {
const MyMoneyAccount& acc = m_file->account(*it_l);
++m_count;
++count;
//this will include an account if it matches the account type and
//if it is still open or it has been set to show closed accounts
if (includeAccount(acc)
&& (!isHidingClosedAccounts() || !acc.isClosed())) {
QString tmpKey;
tmpKey = key + MyMoneyFile::AccountSeperator + acc.name();
QTreeWidgetItem* subItem = selector->newItem(item, acc.name(), tmpKey, acc.id());
subItem->setIcon(0, acc.accountPixmap());
if (acc.value("PreferredAccount") == "Yes"
&& m_typeList.contains(acc.accountType())) {
selector->newItem(m_favorites, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountPixmap());;
}
if (acc.accountList().count() > 0) {
subItem->setExpanded(true);
count += loadSubAccounts(selector, subItem, tmpKey, acc.accountList());
}
// the item is not selectable if it has been added only because a subaccount matches the type
if (!m_typeList.contains(acc.accountType())) {
selector->setSelectable(subItem, false);
}
subItem->sortChildren(1, Qt::AscendingOrder);
}
}
item->sortChildren(1, Qt::AscendingOrder);
}
}
m_favorites->sortChildren(1, Qt::AscendingOrder);
lv->invisibleRootItem()->sortChildren(1, Qt::AscendingOrder);
// if we don't have a favorite account or the selector is for multi-mode
// we get rid of the favorite entry and subentries.
if (m_favorites->childCount() == 0 || selector->selectionMode() == QTreeWidget::MultiSelection) {
delete m_favorites;
m_favorites = 0;
}
if (lv->itemAt(0, 0)) {
if (currentId.isEmpty()) {
lv->setCurrentItem(lv->itemAt(0, 0));
lv->clearSelection();
} else {
selector->setSelected(currentId);
}
}
selector->update();
return count;
}
int AccountSet::load(kMyMoneyAccountSelector* selector, const QString& baseName, const QList<QString>& accountIdList, const bool clear)
{
int count = 0;
QTreeWidgetItem* item = 0;
m_typeList.clear();
if (clear) {
m_count = 0;
selector->clear();
}
item = selector->newItem(baseName);
++m_count;
QList<QString>::ConstIterator it;
for (it = accountIdList.constBegin(); it != accountIdList.constEnd(); ++it) {
const MyMoneyAccount& acc = m_file->account(*it);
if (acc.isClosed())
continue;
QString tmpKey;
// the first character must be preset. Since we don't know any sort order here, we just use A
tmpKey = QString("A%1%2%3").arg(baseName, MyMoneyFile::AccountSeperator, acc.name());
selector->newItem(item, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountPixmap());
++m_count;
++count;
}
QTreeWidget* lv = selector->listView();
if (lv->itemAt(0, 0)) {
lv->setCurrentItem(lv->itemAt(0, 0));
lv->clearSelection();
}
selector->update();
return count;
}
int AccountSet::loadSubAccounts(kMyMoneyAccountSelector* selector, QTreeWidgetItem* parent, const QString& key, const QStringList& list)
{
QStringList::ConstIterator it_l;
int count = 0;
for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) {
const MyMoneyAccount& acc = m_file->account(*it_l);
// don't include stock accounts if not in expert mode
if (acc.isInvest() && !KMyMoneyGlobalSettings::expertMode())
continue;
//this will include an account if it matches the account type and
//if it is still open or it has been set to show closed accounts
if (includeAccount(acc)
&& (!isHidingClosedAccounts() || !acc.isClosed())) {
QString tmpKey;
tmpKey = key + MyMoneyFile::AccountSeperator + acc.name();
++count;
++m_count;
QTreeWidgetItem* item = selector->newItem(parent, acc.name(), tmpKey, acc.id());
item->setIcon(0, acc.accountPixmap());
if (acc.value("PreferredAccount") == "Yes"
&& m_typeList.contains(acc.accountType())) {
selector->newItem(m_favorites, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountPixmap());
}
if (acc.accountList().count() > 0) {
item->setExpanded(true);
count += loadSubAccounts(selector, item, tmpKey, acc.accountList());
}
// the item is not selectable if it has been added only because a subaccount matches the type
if (!m_typeList.contains(acc.accountType())) {
selector->setSelectable(item, false);
}
item->sortChildren(1, Qt::AscendingOrder);
}
}
return count;
}
bool AccountSet::includeAccount(const MyMoneyAccount& acc)
{
if (m_typeList.contains(acc.accountType()))
return true;
QStringList accounts = acc.accountList();
if (accounts.size() > 0) {
QStringList::ConstIterator it_acc;
for (it_acc = accounts.constBegin(); it_acc != accounts.constEnd(); ++it_acc) {
MyMoneyAccount account = m_file->account(*it_acc);
if (includeAccount(account))
return true;
}
}
return false;
}
diff --git a/kmymoney/widgets/kmymoneyaccountselector.h b/kmymoney/widgets/kmymoneyaccountselector.h
index e2c32bc5a..7cf417bf9 100644
--- a/kmymoney/widgets/kmymoneyaccountselector.h
+++ b/kmymoney/widgets/kmymoneyaccountselector.h
@@ -1,201 +1,201 @@
/***************************************************************************
kmymoneyaccountselector.h
-------------------
begin : Thu Sep 18 2003
copyright : (C) 2003 by Thomas Baumgart
email : Thomas Baumgart <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 KMYMONEYACCOUNTSELECTOR_H
#define KMYMONEYACCOUNTSELECTOR_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
// ----------------------------------------------------------------------------
// KDE Includes
class QPushButton;
// ----------------------------------------------------------------------------
// Project Includes
#include <kmymoneyselector.h>
#include "mymoneyaccount.h"
class MyMoneyFile;
/**
* This class implements an account/category selector. It is based
* on a tree view. Using this widget, one can select one or multiple
* accounts depending on the mode of operation and the set of accounts
* selected to be displayed. (see setSelectionMode()
* and loadList() about the specifics of configuration).
*
* - Single selection mode\n
* In this mode the widget allows to select a single entry out of
* the set of displayed accounts.
*
* - Multi selection mode\n
* In this mode, the widget allows to select one or more entries
* out of the set of displayed accounts. Selection is performed
* by marking the account in the view.
*/
class kMyMoneyAccountSelector : public KMyMoneySelector
{
Q_OBJECT
public:
friend class AccountSet;
explicit kMyMoneyAccountSelector(QWidget *parent = 0, Qt::WindowFlags flags = 0, const bool createButtons = true);
virtual ~kMyMoneyAccountSelector();
/**
* This method returns a list of account ids of those accounts
* currently loaded into the widget. It is possible to select
* a list of specific account types only. In this case, pass
* a list of account types as parameter @p list.
*
* @param list QList of account types to be returned. If this
* list is empty (the default), then the ids of all accounts
* will be returned.
* @return QStringList of account ids
*/
- QStringList accountList(const QList<MyMoneyAccount::accountTypeE>& list = QList<MyMoneyAccount::accountTypeE>()) const;
+ QStringList accountList(const QList<eMyMoney::Account>& list = QList<eMyMoney::Account>()) const;
void setSelectionMode(QTreeWidget::SelectionMode mode);
/**
* This method checks if a given @a item matches the given regular expression @a exp.
*
* @param exp const reference to a regular expression object
* @param item pointer to QListViewItem
*
* @retval true item matches
* @retval false item does not match
*/
virtual bool match(const QRegExp& exp, QTreeWidgetItem* item) const;
/**
* This method returns, if any of the items in the selector contains
* the text @a txt.
*
* @param txt const reference to string to be looked for
* @retval true exact match found
* @retval false no match found
*/
virtual bool contains(const QString& txt) const;
/**
* This method removes all the buttons of the widget
*/
void removeButtons();
public slots:
/**
* This slot selects all items that are currently in
* the account list of the widget.
*/
void slotSelectAllAccounts() {
selectAllItems(true);
};
/**
* This slot deselects all items that are currently in
* the account list of the widget.
*/
void slotDeselectAllAccounts() {
selectAllItems(false);
};
protected:
/**
* This method loads the list of subaccounts as found in the
* @p list and attaches them to the parent widget passed as @p parent.
*
* @param parent pointer to parent widget
* @param list QStringList containing the ids of all subaccounts to load
* @return This method returns the number of accounts loaded into the list
*/
int loadSubAccounts(QTreeWidgetItem* parent, const QStringList& list);
/**
* This is a helper method for selectAllIncomeCategories()
* and selectAllExpenseCategories().
*/
void selectCategories(const bool income, const bool expense);
protected slots:
/**
* This slot selects all income categories
*/
void slotSelectIncomeCategories() {
selectCategories(true, false);
};
/**
* This slot selects all expense categories
*/
void slotSelectExpenseCategories() {
selectCategories(false, true);
};
protected:
QPushButton* m_allAccountsButton;
QPushButton* m_noAccountButton;
QPushButton* m_incomeCategoriesButton;
QPushButton* m_expenseCategoriesButton;
QList<int> m_typeList;
QStringList m_accountList;
};
class AccountSet
{
public:
AccountSet();
- void addAccountType(MyMoneyAccount::accountTypeE type);
- void addAccountGroup(MyMoneyAccount::accountTypeE type);
- void removeAccountType(MyMoneyAccount::accountTypeE type);
+ void addAccountType(eMyMoney::Account type);
+ void addAccountGroup(eMyMoney::Account type);
+ void removeAccountType(eMyMoney::Account type);
void clear();
int load(kMyMoneyAccountSelector* selector);
int load(kMyMoneyAccountSelector* selector, const QString& baseName, const QList<QString>& accountIdList, const bool clear = false);
int count() const {
return m_count;
}
void setHideClosedAccounts(bool _bool) {
m_hideClosedAccounts = _bool;
}
bool isHidingClosedAccounts() const {
return m_hideClosedAccounts;
}
protected:
int loadSubAccounts(kMyMoneyAccountSelector* selector, QTreeWidgetItem* parent, const QString& key, const QStringList& list);
bool includeAccount(const MyMoneyAccount& acc);
private:
int m_count;
MyMoneyFile* m_file;
- QList<MyMoneyAccount::accountTypeE> m_typeList;
+ QList<eMyMoney::Account> m_typeList;
QTreeWidgetItem* m_favorites;
bool m_hideClosedAccounts;
};
#endif
diff --git a/kmymoney/widgets/kmymoneyaccounttreeview.cpp b/kmymoney/widgets/kmymoneyaccounttreeview.cpp
index 60068a48d..2a35cae46 100644
--- a/kmymoney/widgets/kmymoneyaccounttreeview.cpp
+++ b/kmymoney/widgets/kmymoneyaccounttreeview.cpp
@@ -1,224 +1,224 @@
/***************************************************************************
* Copyright 2010 Cristian Onet onet.cristian@gmail.com *
* Copyright 2017 Łukasz Wojniłowicz 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) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 of the license. *
* *
* 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 <http://www.gnu.org/licenses/> *
***************************************************************************/
#include "kmymoneyaccounttreeview.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QHeaderView>
#include <QMouseEvent>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KConfigGroup>
#include <KSharedConfig>
// ----------------------------------------------------------------------------
// Project Includes
#include "models.h"
#include "accountsmodel.h"
#include "accountsviewproxymodel.h"
#include "budgetviewproxymodel.h"
#include "modelenums.h"
KMyMoneyAccountTreeView::KMyMoneyAccountTreeView(QWidget *parent)
: QTreeView(parent), m_view(View::None)
{
setContextMenuPolicy(Qt::CustomContextMenu); // allow context menu to be opened on tree items
header()->setContextMenuPolicy(Qt::CustomContextMenu); // allow context menu to be opened on tree header for columns selection
connect(this, &QWidget::customContextMenuRequested, this, &KMyMoneyAccountTreeView::customContextMenuRequested);
setAllColumnsShowFocus(true);
setAlternatingRowColors(true);
setIconSize(QSize(22, 22));
setSortingEnabled(true);
}
KMyMoneyAccountTreeView::~KMyMoneyAccountTreeView()
{
if (m_view != View::None) {
auto grp = KSharedConfig::openConfig()->group(getConfGrpName(m_view));
const auto columns = header()->saveState();
grp.writeEntry("HeaderState", columns);
QList<int> visColumns;
foreach (const auto column, m_model->getVisibleColumns())
visColumns.append(static_cast<int>(column));
grp.writeEntry("ColumnsSelection", visColumns);
grp.sync();
}
}
AccountsViewProxyModel *KMyMoneyAccountTreeView::init(View view)
{
m_view = view;
if (view != View::Budget)
m_model = new AccountsViewProxyModel(this);
else
m_model = new BudgetViewProxyModel(this);
m_model->addAccountGroup(getVisibleGroups(view));
const auto accountsModel = Models::instance()->accountsModel();
const auto institutionsModel = Models::instance()->institutionsModel();
AccountsModel *sourceModel;
if (view != View::Institutions)
sourceModel = accountsModel;
else
sourceModel = institutionsModel;
foreach (const auto column, readVisibleColumns(view)) {
m_model->setColumnVisibility(column, true);
accountsModel->setColumnVisibility(column, true);
institutionsModel->setColumnVisibility(column, true);
}
m_model->setSourceModel(sourceModel);
m_model->setSourceColumns(sourceModel->getColumns());
setModel(m_model);
connect(this->header(), &QWidget::customContextMenuRequested, m_model, &AccountsViewProxyModel::slotColumnsMenu);
connect(m_model, &AccountsViewProxyModel::columnToggled, this, &KMyMoneyAccountTreeView::columnToggled);
// restore the headers
const auto grp = KSharedConfig::openConfig()->group(getConfGrpName(view));
const auto columnNames = grp.readEntry("HeaderState", QByteArray());
header()->restoreState(columnNames);
return m_model;
}
void KMyMoneyAccountTreeView::mouseDoubleClickEvent(QMouseEvent *event)
{
openIndex(currentIndex());
event->accept();
}
void KMyMoneyAccountTreeView::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
openIndex(currentIndex());
event->accept();
} else {
QTreeView::keyPressEvent(event);
}
}
void KMyMoneyAccountTreeView::openIndex(const QModelIndex &index)
{
if (index.isValid()) {
QVariant data = model()->data(index, (int)eAccountsModel::Role::Account);
if (data.isValid()) {
if (data.canConvert<MyMoneyAccount>()) {
emit openObject(data.value<MyMoneyAccount>());
}
if (data.canConvert<MyMoneyInstitution>()) {
emit openObject(data.value<MyMoneyInstitution>());
}
}
}
}
QString KMyMoneyAccountTreeView::getConfGrpName(const View view)
{
switch (view) {
case View::Institutions:
return QStringLiteral("KInstitutionsView");
case View::Accounts:
return QStringLiteral("KAccountsView");
case View::Categories:
return QStringLiteral("KCategoriesView");
case View::Budget:
return QStringLiteral("KBudgetsView");
default:
return QString();
}
}
void KMyMoneyAccountTreeView::customContextMenuRequested(const QPoint)
{
const auto index = model()->index(currentIndex().row(), (int)eAccountsModel::Column::Account, currentIndex().parent());
if (index.isValid() && (model()->flags(index) & Qt::ItemIsSelectable)) {
const auto data = model()->data(index, (int)eAccountsModel::Role::Account);
if (data.isValid()) {
if (data.canConvert<MyMoneyAccount>()) {
emit selectObject(data.value<MyMoneyAccount>());
emit openContextMenu(data.value<MyMoneyAccount>());
}
if (data.canConvert<MyMoneyInstitution>()) {
emit selectObject(data.value<MyMoneyInstitution>());
emit openContextMenu(data.value<MyMoneyInstitution>());
}
}
}
}
void KMyMoneyAccountTreeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
QTreeView::selectionChanged(selected, deselected);
if (!selected.empty()) {
auto indexes = selected.front().indexes();
if (!indexes.empty()) {
const auto data = model()->data(model()->index(indexes.front().row(), (int)eAccountsModel::Column::Account, indexes.front().parent()), (int)eAccountsModel::Role::Account);
if (data.isValid()) {
if (data.canConvert<MyMoneyAccount>()) {
emit selectObject(data.value<MyMoneyAccount>());
}
if (data.canConvert<MyMoneyInstitution>()) {
emit selectObject(data.value<MyMoneyInstitution>());
}
// an object was successfully selected
return;
}
}
}
// since no object was selected reset the object selection
emit selectObject(MyMoneyAccount());
emit selectObject(MyMoneyInstitution());
}
-QVector<MyMoneyAccount::_accountTypeE> KMyMoneyAccountTreeView::getVisibleGroups(const View view)
+QVector<eMyMoney::Account> KMyMoneyAccountTreeView::getVisibleGroups(const View view)
{
switch (view) {
case View::Institutions:
case View::Accounts:
- return QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Asset, MyMoneyAccount::Liability, MyMoneyAccount::Equity};
+ return QVector<eMyMoney::Account> {eMyMoney::Account::Asset, eMyMoney::Account::Liability, eMyMoney::Account::Equity};
case View::Categories:
case View::Budget:
- return QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Income, MyMoneyAccount::Expense};
+ return QVector<eMyMoney::Account> {eMyMoney::Account::Income, eMyMoney::Account::Expense};
default:
- return QVector<MyMoneyAccount::_accountTypeE> ();
+ return QVector<eMyMoney::Account> ();
}
}
QSet<eAccountsModel::Column> KMyMoneyAccountTreeView::readVisibleColumns(const View view)
{
QSet<eAccountsModel::Column> columns;
const auto grp = KSharedConfig::openConfig()->group(getConfGrpName(view));
const auto cfgColumns = grp.readEntry("ColumnsSelection", QList<int>());
columns.insert(eAccountsModel::Column::Account);
foreach (const auto column, cfgColumns)
columns.insert(static_cast<eAccountsModel::Column>(column));
return columns;
}
diff --git a/kmymoney/widgets/kmymoneyaccounttreeview.h b/kmymoney/widgets/kmymoneyaccounttreeview.h
index 88853acab..8b6074f3b 100644
--- a/kmymoney/widgets/kmymoneyaccounttreeview.h
+++ b/kmymoney/widgets/kmymoneyaccounttreeview.h
@@ -1,103 +1,103 @@
/***************************************************************************
* Copyright 2010 Cristian Onet onet.cristian@gmail.com *
* Copyright 2017 Łukasz Wojniłowicz 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) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 of the license. *
* *
* 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 <http://www.gnu.org/licenses/> *
***************************************************************************/
#ifndef KMYMONEYACCOUNTTREEVIEW_H
#define KMYMONEYACCOUNTTREEVIEW_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QTreeView>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyaccount.h"
#include "viewenums.h"
namespace eAccountsModel {
enum class Column;
}
class AccountsViewProxyModel;
class MyMoneyObject;
/**
* This view was created to handle the actions that could be performed with the accounts.
*/
class KMyMoneyAccountTreeView : public QTreeView
{
Q_OBJECT
public:
KMyMoneyAccountTreeView(QWidget *parent = nullptr);
~KMyMoneyAccountTreeView();
AccountsViewProxyModel *init(View view);
protected:
void mouseDoubleClickEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
protected slots:
void customContextMenuRequested(const QPoint);
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
signals:
/**
* This signal serves as proxy for KMyMoneyAccountTree::selectObject()
*
* @param obj const reference to object
*/
void selectObject(const MyMoneyObject& obj);
/**
* This signal serves as proxy for
* KMyMoneyAccountTree::openContextMenu(const MyMoneyObject&)
*
* @param obj const reference to object
*/
void openContextMenu(const MyMoneyObject& obj);
/**
* This signal is emitted whenever the user requests to open an object
*
* @param obj reference to actual MyMoneyObject (is either
* MyMoneyAccount or MyMoneyInstitution depending on selected item)
*/
void openObject(const MyMoneyObject& obj);
void columnToggled(const eAccountsModel::Column column, const bool show);
private:
void openIndex(const QModelIndex &index);
static QString getConfGrpName(const View view);
QSet<eAccountsModel::Column> readVisibleColumns(const View view);
- QVector<MyMoneyAccount::_accountTypeE> getVisibleGroups(const View view);
+ QVector<eMyMoney::Account> getVisibleGroups(const View view);
AccountsViewProxyModel *m_model;
View m_view;
};
#endif // KMYMONEYACCOUNTTREEVIEW_H
diff --git a/kmymoney/widgets/kmymoneybriefschedule.cpp b/kmymoney/widgets/kmymoneybriefschedule.cpp
index d9d370cca..10e57c2a5 100644
--- a/kmymoney/widgets/kmymoneybriefschedule.cpp
+++ b/kmymoney/widgets/kmymoneybriefschedule.cpp
@@ -1,180 +1,180 @@
/***************************************************************************
kmymoneybriefschedule.cpp - description
-------------------
begin : Sun Jul 6 2003
copyright : (C) 2000-2003 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "kmymoneybriefschedule.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QLabel>
#include <QToolButton>
#include <QList>
#include <QPushButton>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyschedule.h"
#include "kmymoneyutils.h"
#include "icons/icons.h"
using namespace Icons;
KMyMoneyBriefSchedule::KMyMoneyBriefSchedule(QWidget *parent)
: kScheduleBriefWidget(parent/*,name, Qt::WStyle_Customize | Qt::WStyle_NoBorder*/),
m_index(0)
{
m_nextButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ArrowRight]));
m_prevButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ArrowLeft]));
connect(m_prevButton, SIGNAL(clicked()), this, SLOT(slotPrevClicked()));
connect(m_nextButton, SIGNAL(clicked()), this, SLOT(slotNextClicked()));
connect(m_closeButton, SIGNAL(clicked()), this, SLOT(hide()));
connect(m_skipButton, SIGNAL(clicked()), this, SLOT(slotSkipClicked()));
connect(m_buttonEnter, SIGNAL(clicked()), this, SLOT(slotEnterClicked()));
KGuiItem skipGuiItem(i18n("&Skip"),
QIcon::fromTheme(g_Icons[Icon::MediaSeekForward]),
i18n("Skip this transaction"),
i18n("Use this button to skip this transaction"));
KGuiItem::assign(m_skipButton, skipGuiItem);
KGuiItem enterGuiItem(i18n("&Enter"),
QIcon::fromTheme(g_Icons[Icon::KeyEnter]),
i18n("Record this transaction into the register"),
i18n("Use this button to record this transaction"));
KGuiItem::assign(m_buttonEnter, enterGuiItem);
}
KMyMoneyBriefSchedule::~KMyMoneyBriefSchedule()
{
}
void KMyMoneyBriefSchedule::setSchedules(QList<MyMoneySchedule> list, const QDate& date)
{
m_scheduleList = list;
m_date = date;
m_index = 0;
if (list.count() >= 1) {
loadSchedule();
}
}
void KMyMoneyBriefSchedule::loadSchedule()
{
try {
if (m_index < m_scheduleList.count()) {
MyMoneySchedule sched = m_scheduleList[m_index];
m_indexLabel->setText(i18n("%1 of %2", m_index + 1, m_scheduleList.count()));
m_name->setText(sched.name());
m_type->setText(KMyMoneyUtils::scheduleTypeToString(sched.type()));
m_account->setText(sched.account().name());
QString text;
MyMoneyMoney amount = sched.transaction().splitByAccount(sched.account().id()).value();
amount = amount.abs();
if (sched.willEnd()) {
int transactions = sched.paymentDates(m_date, sched.endDate()).count() - 1;
text = i18np("Payment on %2 for %3 with %1 transaction remaining occurring %4.",
"Payment on %2 for %3 with %1 transactions remaining occurring %4.",
transactions,
QLocale().toString(m_date, QLocale::ShortFormat),
amount.formatMoney(sched.account().fraction()),
i18n(sched.occurrenceToString().toLatin1()));
} else {
text = i18n("Payment on %1 for %2 occurring %3.",
QLocale().toString(m_date, QLocale::ShortFormat),
amount.formatMoney(sched.account().fraction()),
i18n(sched.occurrenceToString().toLatin1()));
}
if (m_date < QDate::currentDate()) {
if (sched.isOverdue()) {
QDate startD = (sched.lastPayment().isValid()) ?
sched.lastPayment() :
sched.startDate();
if (m_date.isValid())
startD = m_date;
int days = startD.daysTo(QDate::currentDate());
int transactions = sched.paymentDates(startD, QDate::currentDate()).count();
text += "<br><font color=red>";
text += i18np("%1 day overdue", "%1 days overdue", days);
text += QString(" ");
text += i18np("(%1 occurrence.)", "(%1 occurrences.)", transactions);
text += "</color>";
}
}
m_details->setText(text);
m_prevButton->setEnabled(true);
m_nextButton->setEnabled(true);
- m_skipButton->setEnabled(sched.occurrencePeriod() != MyMoneySchedule::OCCUR_ONCE);
+ m_skipButton->setEnabled(sched.occurrencePeriod() != eMyMoney::Schedule::Occurrence::Once);
if (m_index == 0)
m_prevButton->setEnabled(false);
if (m_index == (m_scheduleList.count() - 1))
m_nextButton->setEnabled(false);
}
} catch (const MyMoneyException &) {
}
}
void KMyMoneyBriefSchedule::slotPrevClicked()
{
if (m_index >= 1) {
--m_index;
loadSchedule();
}
}
void KMyMoneyBriefSchedule::slotNextClicked()
{
if (m_index < (m_scheduleList.count() - 1)) {
m_index++;
loadSchedule();
}
}
void KMyMoneyBriefSchedule::slotEnterClicked()
{
hide();
emit enterClicked(m_scheduleList[m_index], m_date);
}
void KMyMoneyBriefSchedule::slotSkipClicked()
{
hide();
emit skipClicked(m_scheduleList[m_index], m_date);
}
diff --git a/kmymoney/widgets/kmymoneymvccombo.cpp b/kmymoney/widgets/kmymoneymvccombo.cpp
index 803e00b27..3fe087ebb 100644
--- a/kmymoney/widgets/kmymoneymvccombo.cpp
+++ b/kmymoney/widgets/kmymoneymvccombo.cpp
@@ -1,818 +1,819 @@
/***************************************************************************
kmymoneymvccombo.cpp - description
-------------------
begin : Sat Jan 09 2010
copyright : (C) 2010 by Thomas Baumgart <ipwizard@users.sourceforge.net>
Cristian Onet <cristian.onet@gmail.com>
Alvaro Soliverez <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 "kmymoneymvccombo.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QDebug>
#include <QApplication>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QAbstractItemView>
#include <QHBoxLayout>
#include <QFrame>
#include <QLabel>
#include <QToolButton>
#include <QMetaMethod>
#include <QApplication>
#include <QCompleter>
#include <QFocusEvent>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KLineEdit>
#include <KMessageBox>
// ----------------------------------------------------------------------------
// Project Includes
#include "icons/icons.h"
#include "mymoneyschedule.h"
#include "mymoneysplit.h"
#include "mymoneytransactionfilter.h"
using namespace Icons;
+using namespace eMyMoney;
class KMyMoneyMVCCombo::Private
{
public:
Private() :
m_canCreateObjects(false),
m_inFocusOutEvent(false),
m_completer(0) {}
/**
* Flag to control object creation. Use
* KMyMoneyMVCCombo::setSuppressObjectCreation()
* to modify it's setting. Defaults to @a false.
*/
bool m_canCreateObjects;
/**
* Flag to check whether a focusOutEvent processing is underway or not
*/
bool m_inFocusOutEvent;
QCompleter *m_completer;
};
KMyMoneyMVCCombo::KMyMoneyMVCCombo(QWidget* parent) :
KComboBox(parent),
d(new Private)
{
view()->setAlternatingRowColors(true);
connect(this, SIGNAL(activated(int)), SLOT(activated(int)));
}
KMyMoneyMVCCombo::KMyMoneyMVCCombo(bool editable, QWidget* parent) :
KComboBox(editable, parent),
d(new Private)
{
d->m_completer = new QCompleter(this);
d->m_completer->setCaseSensitivity(Qt::CaseInsensitive);
d->m_completer->setModel(model());
setCompleter(d->m_completer);
// setSubstringSearch(!KMyMoneySettings::stringMatchFromStart());
view()->setAlternatingRowColors(true);
setInsertPolicy(QComboBox::NoInsert); // don't insert new objects due to object creation
connect(this, SIGNAL(activated(int)), SLOT(activated(int)));
}
KMyMoneyMVCCombo::~KMyMoneyMVCCombo()
{
delete d;
}
void KMyMoneyMVCCombo::setEditable(bool editable)
{
KComboBox::setEditable(editable);
if(editable) {
if(!d->m_completer) {
d->m_completer = new QCompleter(this);
d->m_completer->setCaseSensitivity(Qt::CaseInsensitive);
d->m_completer->setModel(model());
}
setCompleter(d->m_completer);
}
}
void KMyMoneyMVCCombo::setSubstringSearch(bool enabled)
{
d->m_completer->setCompletionMode(QCompleter::PopupCompletion);
d->m_completer->setModel(model());
d->m_completer->setFilterMode(enabled ? Qt::MatchContains : Qt::MatchStartsWith);
}
void KMyMoneyMVCCombo::setSubstringSearchForChildren(QWidget*const widget, bool enabled)
{
Q_CHECK_PTR(widget);
QList<KMyMoneyMVCCombo *> comboList;
comboList = widget->findChildren<KMyMoneyMVCCombo *>();
foreach (KMyMoneyMVCCombo *combo, comboList) {
combo->setSubstringSearch(enabled);
}
}
void KMyMoneyMVCCombo::setPlaceholderText(const QString& hint) const
{
KLineEdit* le = qobject_cast<KLineEdit*>(lineEdit());
if (le) {
le->setPlaceholderText(hint);
}
}
const QString& KMyMoneyMVCCombo::selectedItem() const
{
QVariant data = itemData(currentIndex());
if (data.isValid())
m_id = data.toString();
else
m_id.clear();
return m_id;
}
void KMyMoneyMVCCombo::setSelectedItem(const QString& id)
{
m_id = id;
setCurrentIndex(findData(QVariant(m_id)));
}
void KMyMoneyMVCCombo::activated(int index)
{
QVariant data = itemData(index);
if (data.isValid()) {
m_id = data.toString();
emit itemSelected(m_id);
}
}
void KMyMoneyMVCCombo::connectNotify(const QMetaMethod & signal)
{
if (signal != QMetaMethod::fromSignal(&KMyMoneyMVCCombo::createItem)) {
d->m_canCreateObjects = true;
}
}
void KMyMoneyMVCCombo::disconnectNotify(const QMetaMethod & signal)
{
if (signal != QMetaMethod::fromSignal(&KMyMoneyMVCCombo::createItem)) {
d->m_canCreateObjects = false;
}
}
void KMyMoneyMVCCombo::focusOutEvent(QFocusEvent* e)
{
// when showing m_completion we'll receive a focus out event even if the focus
// will still remain at this widget since this widget is the completion's focus proxy
// so ignore the focus out event caused by showin a widget of type Qt::Popup
if (e->reason() == Qt::PopupFocusReason)
return;
if (d->m_inFocusOutEvent) {
KComboBox::focusOutEvent(e);
return;
}
//check if the focus went to a widget in TransactionFrom or in the Register
if (e->reason() == Qt::MouseFocusReason) {
QObject *w = this->parent();
QObject *q = qApp->focusWidget()->parent();
// KMyMoneyTagCombo is inside KTagContainer, KMyMoneyPayeeCombo isn't it
if (w->inherits("KTagContainer"))
w = w->parent();
while (q && q->objectName() != "qt_scrollarea_viewport")
q = q->parent();
if (q != w && qApp->focusWidget()->objectName() != "register") {
KComboBox::focusOutEvent(e);
return;
}
}
d->m_inFocusOutEvent = true;
if (isEditable() && !currentText().isEmpty() && e->reason() != Qt::ActiveWindowFocusReason) {
if (d->m_canCreateObjects) {
// in case we tab out, we make sure that if the current completion
// contains the current text that we set the current text to
// the full completion text but only if the completion box is visible.
// BUG 254984 is resolved with the visbility check
if (e->reason() != Qt::MouseFocusReason) {
if (d->m_completer->popup() && d->m_completer->popup()->isVisible()
&& d->m_completer->currentCompletion().contains(currentText(), Qt::CaseInsensitive)) {
lineEdit()->setText(d->m_completer->currentCompletion());
}
}
//check if the current text is contained in the internal list, if not ask the user if want to create a new item.
checkCurrentText();
// else if we cannot create objects, and the current text is not
// in the list, then we clear the text and the selection.
} else if (!contains(currentText())) {
clearEditText();
}
//this is to cover the case when you highlight an item but don't activate it with Enter
if (currentText() != itemText(currentIndex())) {
setCurrentIndex(findText(currentText(), Qt::MatchExactly));
emit activated(currentIndex());
}
}
KComboBox::focusOutEvent(e);
// force update of hint and id if there is no text in the widget
if (isEditable() && currentText().isEmpty()) {
QString id = m_id;
m_id.clear();
if (!id.isEmpty())
emit itemSelected(m_id);
update();
}
d->m_inFocusOutEvent = false;
// This is used only be KMyMoneyTagCombo at this time
emit lostFocus();
}
void KMyMoneyMVCCombo::checkCurrentText()
{
if (!contains(currentText())) {
QString id;
// annouce that we go into a possible dialog to create an object
// This can be used by upstream widgets to disable filters etc.
emit objectCreation(true);
emit createItem(currentText(), id);
// Announce that we return from object creation
emit objectCreation(false);
// update the field to a possibly created object
m_id = id;
setCurrentTextById(id);
}
}
void KMyMoneyMVCCombo::setCurrentTextById(const QString& id)
{
clearEditText();
if (!id.isEmpty()) {
int index = findData(QVariant(id), Qt::UserRole, Qt::MatchExactly);
if (index > -1) {
setCompletedText(itemText(index));
setEditText(itemText(index));
setCurrentIndex(index);
}
}
}
void KMyMoneyMVCCombo::protectItem(int id, bool protect)
{
QStandardItemModel* standardModel = qobject_cast<QStandardItemModel*> (model());
QStandardItem* standardItem = standardModel->item(id);
standardItem->setSelectable(!protect);
}
KMyMoneyPayeeCombo::KMyMoneyPayeeCombo(QWidget* parent) :
KMyMoneyMVCCombo(true, parent)
{
}
void KMyMoneyPayeeCombo::loadPayees(const QList<MyMoneyPayee>& list)
{
clear();
//add a blank item, since the field is optional
addItem(QString(), QVariant(QString()));
//add all payees
QList<MyMoneyPayee>::const_iterator it;
for (it = list.constBegin(); it != list.constEnd(); ++it) {
addItem((*it).name(), QVariant((*it).id()));
}
//sort the model, which will sort the list in the combo
model()->sort(Qt::DisplayRole, Qt::AscendingOrder);
//set the text to empty and the index to the first item on the list
setCurrentIndex(0);
clearEditText();
}
KMyMoneyTagCombo::KMyMoneyTagCombo(QWidget* parent) :
KMyMoneyMVCCombo(true, parent)
{
}
void KMyMoneyTagCombo::loadTags(const QList<MyMoneyTag>& list)
{
clear();
//add a blank item, since the field is optional
addItem(QString(), QVariant(QString()));
//add all not closed tags
QList<MyMoneyTag>::const_iterator it;
for (it = list.constBegin(); it != list.constEnd(); ++it) {
if (!(*it).isClosed())
addItem((*it).name(), QVariant((*it).id()));
else {
m_closedIdList.append((*it).id());
m_closedTagNameList.append((*it).name());
}
}
//sort the model, which will sort the list in the combo
model()->sort(Qt::DisplayRole, Qt::AscendingOrder);
//set the text to empty and the index to the first item on the list
setCurrentIndex(0);
clearEditText();
}
void KMyMoneyTagCombo::setUsedTagList(QList<QString>& usedIdList, QList<QString>& usedTagNameList)
{
m_usedIdList = usedIdList;
m_usedTagNameList = usedTagNameList;
for (int i = 0; i < m_usedIdList.size(); ++i) {
int index = findData(QVariant(m_usedIdList.at(i)), Qt::UserRole, Qt::MatchExactly);
if (index != -1) removeItem(index);
}
}
void KMyMoneyTagCombo::checkCurrentText()
{
if (!contains(currentText())) {
if (m_closedTagNameList.contains(currentText())) {
// Tell the user what's happened
QString msg = QString("<qt>") + i18n("Closed tags cannot be used.") + QString("</qt>");
KMessageBox::information(this, msg, i18n("Closed tag"), "Closed tag");
setCurrentText();
return;
} else if (m_usedTagNameList.contains(currentText())) {
// Tell the user what's happened
QString msg = QString("<qt>") + i18n("The tag is already present.") + QString("</qt>");
KMessageBox::information(this, msg, i18n("Duplicate tag"), "Duplicate tag");
setCurrentText();
return;
}
QString id;
// annouce that we go into a possible dialog to create an object
// This can be used by upstream widgets to disable filters etc.
emit objectCreation(true);
emit createItem(currentText(), id);
// Announce that we return from object creation
emit objectCreation(false);
// update the field to a possibly created object
//m_id = id;
setCurrentTextById(id);
}
}
KTagLabel::KTagLabel(const QString& id, const QString& name, QWidget* parent) :
QFrame(parent)
{
QToolButton *t = new QToolButton(this);
t->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogClose]));
t->setAutoRaise(true);
QLabel *l = new QLabel(name, this);
m_tagId = id;
QHBoxLayout *layout = new QHBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
this->setLayout(layout);
layout->addWidget(t);
layout->addWidget(l);
connect(t, SIGNAL(clicked(bool)), this, SIGNAL(clicked(bool)));
//this->setFrameStyle(QFrame::Panel | QFrame::Plain);
}
KTagContainer::KTagContainer(QWidget* parent) :
QWidget(parent)
{
m_tagCombo = new KMyMoneyTagCombo;
QHBoxLayout *layout = new QHBoxLayout;
layout->setContentsMargins(0, 0, 5, 0);
layout->setSpacing(0);
layout->addWidget(m_tagCombo, 100);
this->setLayout(layout);
this->setFocusProxy(m_tagCombo);
connect(m_tagCombo, SIGNAL(lostFocus()), this, SLOT(slotAddTagWidget()));
}
void KTagContainer::loadTags(const QList<MyMoneyTag>& list)
{
m_list = list;
m_tagCombo->loadTags(list);
}
const QList<QString> KTagContainer::selectedTags()
{
return m_tagIdList;
}
void KTagContainer::addTagWidget(const QString& id)
{
if (id.isNull() || m_tagIdList.contains(id))
return;
const QString tagName = m_tagCombo->itemText(m_tagCombo->findData(QVariant(id), Qt::UserRole, Qt::MatchExactly));
KTagLabel *t = new KTagLabel(id, tagName, this);
connect(t, SIGNAL(clicked(bool)), this, SLOT(slotRemoveTagWidget()));
m_tagLabelList.append(t);
m_tagNameList.append(tagName);
m_tagIdList.append(id);
this->layout()->addWidget(t);
m_tagCombo->loadTags(m_list);
m_tagCombo->setUsedTagList(m_tagIdList, m_tagNameList);
m_tagCombo->setCurrentIndex(0);
m_tagCombo->setFocus();
}
void KTagContainer::RemoveAllTagWidgets()
{
m_tagIdList.clear();
m_tagNameList.clear();
while (!m_tagLabelList.isEmpty())
delete m_tagLabelList.takeLast();
m_tagCombo->loadTags(m_list);
m_tagCombo->setUsedTagList(m_tagIdList, m_tagNameList);
m_tagCombo->setCurrentIndex(0);
}
void KTagContainer::slotAddTagWidget()
{
addTagWidget(m_tagCombo->selectedItem());
}
void KTagContainer::slotRemoveTagWidget()
{
this->tagCombo()->setFocus();
KTagLabel *t = (KTagLabel *)sender();
int index = m_tagLabelList.indexOf(t);
m_tagLabelList.removeAt(index);
m_tagIdList.removeAt(index);
m_tagNameList.removeAt(index);
delete t;
m_tagCombo->loadTags(m_list);
m_tagCombo->setUsedTagList(m_tagIdList, m_tagNameList);
m_tagCombo->setCurrentIndex(0);
}
KMyMoneyReconcileCombo::KMyMoneyReconcileCombo(QWidget* w) :
KMyMoneyMVCCombo(false, w)
{
// add the items in reverse order of appearance (see KMyMoneySelector::newItem() for details)
addItem(i18n("Reconciled"), QVariant("R"));
addItem(i18nc("Reconciliation state 'Cleared'", "Cleared"), QVariant("C"));
addItem(i18n("Not reconciled"), QVariant(" "));
addItem(" ", QVariant("U"));
connect(this, SIGNAL(itemSelected(QString)), this, SLOT(slotSetState(QString)));
}
void KMyMoneyReconcileCombo::slotSetState(const QString& state)
{
setSelectedItem(state);
}
void KMyMoneyReconcileCombo::removeDontCare()
{
//Remove unknown state
removeItem(3);
}
void KMyMoneyReconcileCombo::setState(MyMoneySplit::reconcileFlagE state)
{
QString id;
switch (state) {
case MyMoneySplit::NotReconciled:
id = ' ';
break;
case MyMoneySplit::Cleared:
id = 'C';
break;
case MyMoneySplit::Reconciled:
id = 'R';
break;
case MyMoneySplit::Frozen:
id = 'F';
break;
case MyMoneySplit::Unknown:
id = 'U';
break;
default:
qDebug() << "Unknown reconcile state '" << state << "' in KMyMoneyReconcileCombo::setState()\n";
break;
}
setSelectedItem(id);
}
MyMoneySplit::reconcileFlagE KMyMoneyReconcileCombo::state() const
{
MyMoneySplit::reconcileFlagE state = MyMoneySplit::NotReconciled;
QVariant data = itemData(currentIndex());
QString dataVal;
if (data.isValid())
dataVal = data.toString();
else
return state;
if (!dataVal.isEmpty()) {
if (dataVal == "C")
state = MyMoneySplit::Cleared;
if (dataVal == "R")
state = MyMoneySplit::Reconciled;
if (dataVal == "F")
state = MyMoneySplit::Frozen;
if (dataVal == "U")
state = MyMoneySplit::Unknown;
}
return state;
}
-KMyMoneyCashFlowCombo::KMyMoneyCashFlowCombo(QWidget* w, MyMoneyAccount::accountTypeE accountType) :
+KMyMoneyCashFlowCombo::KMyMoneyCashFlowCombo(QWidget* w, Account accountType) :
KMyMoneyMVCCombo(false, w),
m_dir(KMyMoneyRegister::Unknown)
{
addItem(" ", QVariant(KMyMoneyRegister::Unknown));
- if (accountType == MyMoneyAccount::Income || accountType == MyMoneyAccount::Expense) {
+ if (accountType == Account::Income || accountType == Account::Expense) {
// this is used for income/expense accounts to just show the reverse sense
addItem(i18nc("Activity for income categories", "Received"), QVariant(KMyMoneyRegister::Payment));
addItem(i18nc("Activity for expense categories", "Paid"), QVariant(KMyMoneyRegister::Deposit));
} else {
addItem(i18n("Pay to"), QVariant(KMyMoneyRegister::Payment));
addItem(i18n("From"), QVariant(KMyMoneyRegister::Deposit));
}
connect(this, SIGNAL(itemSelected(QString)), this, SLOT(slotSetDirection(QString)));
}
void KMyMoneyCashFlowCombo::setDirection(KMyMoneyRegister::CashFlowDirection dir)
{
m_dir = dir;
QString num;
setSelectedItem(num.setNum(dir));
}
void KMyMoneyCashFlowCombo::slotSetDirection(const QString& id)
{
QString num;
for (int i = KMyMoneyRegister::Deposit; i <= KMyMoneyRegister::Unknown; ++i) {
num.setNum(i);
if (num == id) {
m_dir = static_cast<KMyMoneyRegister::CashFlowDirection>(i);
break;
}
}
emit directionSelected(m_dir);
update();
}
void KMyMoneyCashFlowCombo::removeDontCare()
{
removeItem(findData(QVariant(KMyMoneyRegister::Unknown), Qt::UserRole, Qt::MatchExactly));
}
KMyMoneyActivityCombo::KMyMoneyActivityCombo(QWidget* w) :
KMyMoneyMVCCombo(false, w),
m_activity(MyMoneySplit::UnknownTransactionType)
{
addItem(i18n("Buy shares"), QVariant(MyMoneySplit::BuyShares));
addItem(i18n("Sell shares"), QVariant(MyMoneySplit::SellShares));
addItem(i18n("Dividend"), QVariant(MyMoneySplit::Dividend));
addItem(i18n("Reinvest dividend"), QVariant(MyMoneySplit::ReinvestDividend));
addItem(i18n("Yield"), QVariant(MyMoneySplit::Yield));
addItem(i18n("Add shares"), QVariant(MyMoneySplit::AddShares));
addItem(i18n("Remove shares"), QVariant(MyMoneySplit::RemoveShares));
addItem(i18n("Split shares"), QVariant(MyMoneySplit::SplitShares));
addItem(i18n("Interest Income"), QVariant(MyMoneySplit::InterestIncome));
connect(this, SIGNAL(itemSelected(QString)), this, SLOT(slotSetActivity(QString)));
}
void KMyMoneyActivityCombo::setActivity(MyMoneySplit::investTransactionTypeE activity)
{
m_activity = activity;
QString num;
setSelectedItem(num.setNum(activity));
}
void KMyMoneyActivityCombo::slotSetActivity(const QString& id)
{
QString num;
for (int i = MyMoneySplit::BuyShares; i <= MyMoneySplit::InterestIncome; ++i) {
num.setNum(i);
if (num == id) {
m_activity = static_cast<MyMoneySplit::investTransactionTypeE>(i);
break;
}
}
emit activitySelected(m_activity);
update();
}
KMyMoneyGeneralCombo::KMyMoneyGeneralCombo(QWidget* w) :
KComboBox(w)
{
connect(this, SIGNAL(highlighted(int)), this, SLOT(slotChangeItem(int)));
}
KMyMoneyGeneralCombo::~KMyMoneyGeneralCombo()
{
}
void KMyMoneyGeneralCombo::setCurrentItem(int id)
{
setCurrentIndex(findData(QVariant(id), Qt::UserRole, Qt::MatchExactly));
}
int KMyMoneyGeneralCombo::currentItem() const
{
return itemData(currentIndex()).toInt();
}
void KMyMoneyGeneralCombo::clear()
{
KComboBox::clear();
}
void KMyMoneyGeneralCombo::insertItem(const QString& txt, int id, int idx)
{
KComboBox::insertItem(idx, txt, QVariant(id));
}
void KMyMoneyGeneralCombo::removeItem(int id)
{
KComboBox::removeItem(findData(QVariant(id), Qt::UserRole, Qt::MatchExactly));
}
void KMyMoneyGeneralCombo::slotChangeItem(int idx)
{
emit itemSelected(itemData(idx).toInt());
}
KMyMoneyPeriodCombo::KMyMoneyPeriodCombo(QWidget* parent) :
KMyMoneyGeneralCombo(parent)
{
insertItem(i18n("All dates"), MyMoneyTransactionFilter::allDates);
insertItem(i18n("As of today"), MyMoneyTransactionFilter::asOfToday);
insertItem(i18n("Today"), MyMoneyTransactionFilter::today);
insertItem(i18n("Current month"), MyMoneyTransactionFilter::currentMonth);
insertItem(i18n("Current quarter"), MyMoneyTransactionFilter::currentQuarter);
insertItem(i18n("Current year"), MyMoneyTransactionFilter::currentYear);
insertItem(i18n("Current fiscal year"), MyMoneyTransactionFilter::currentFiscalYear);
insertItem(i18n("Month to date"), MyMoneyTransactionFilter::monthToDate);
insertItem(i18n("Year to date"), MyMoneyTransactionFilter::yearToDate);
insertItem(i18n("Year to month"), MyMoneyTransactionFilter::yearToMonth);
insertItem(i18n("Last month"), MyMoneyTransactionFilter::lastMonth);
insertItem(i18n("Last year"), MyMoneyTransactionFilter::lastYear);
insertItem(i18n("Last fiscal year"), MyMoneyTransactionFilter::lastFiscalYear);
insertItem(i18n("Last 7 days"), MyMoneyTransactionFilter::last7Days);
insertItem(i18n("Last 30 days"), MyMoneyTransactionFilter::last30Days);
insertItem(i18n("Last 3 months"), MyMoneyTransactionFilter::last3Months);
insertItem(i18n("Last quarter"), MyMoneyTransactionFilter::lastQuarter);
insertItem(i18n("Last 6 months"), MyMoneyTransactionFilter::last6Months);
insertItem(i18n("Last 11 months"), MyMoneyTransactionFilter::last11Months);
insertItem(i18n("Last 12 months"), MyMoneyTransactionFilter::last12Months);
insertItem(i18n("Next 7 days"), MyMoneyTransactionFilter::next7Days);
insertItem(i18n("Next 30 days"), MyMoneyTransactionFilter::next30Days);
insertItem(i18n("Next 3 months"), MyMoneyTransactionFilter::next3Months);
insertItem(i18n("Next quarter"), MyMoneyTransactionFilter::nextQuarter);
insertItem(i18n("Next 6 months"), MyMoneyTransactionFilter::next6Months);
insertItem(i18n("Next 12 months"), MyMoneyTransactionFilter::next12Months);
insertItem(i18n("Next 18 months"), MyMoneyTransactionFilter::next18Months);
insertItem(i18n("Last 3 months to next 3 months"), MyMoneyTransactionFilter::last3ToNext3Months);
insertItem(i18n("User defined"), MyMoneyTransactionFilter::userDefined);
}
void KMyMoneyPeriodCombo::setCurrentItem(MyMoneyTransactionFilter::dateOptionE id)
{
if (id >= MyMoneyTransactionFilter::dateOptionCount)
id = MyMoneyTransactionFilter::userDefined;
KMyMoneyGeneralCombo::setCurrentItem(id);
}
MyMoneyTransactionFilter::dateOptionE KMyMoneyPeriodCombo::currentItem() const
{
return static_cast<MyMoneyTransactionFilter::dateOptionE>(KMyMoneyGeneralCombo::currentItem());
}
QDate KMyMoneyPeriodCombo::start(MyMoneyTransactionFilter::dateOptionE id)
{
QDate start, end;
MyMoneyTransactionFilter::translateDateRange(id, start, end);
return start;
}
QDate KMyMoneyPeriodCombo::end(MyMoneyTransactionFilter::dateOptionE id)
{
QDate start, end;
MyMoneyTransactionFilter::translateDateRange(id, start, end);
return end;
}
#if 0
void KMyMoneyPeriodCombo::dates(QDate& start, QDate& end, MyMoneyTransactionFilter::dateOptionE id)
{
}
#endif
KMyMoneyOccurrenceCombo::KMyMoneyOccurrenceCombo(QWidget* parent) :
KMyMoneyGeneralCombo(parent)
{
}
-MyMoneySchedule::occurrenceE KMyMoneyOccurrenceCombo::currentItem() const
+Schedule::Occurrence KMyMoneyOccurrenceCombo::currentItem() const
{
- return static_cast<MyMoneySchedule::occurrenceE>(KMyMoneyGeneralCombo::currentItem());
+ return static_cast<Schedule::Occurrence>(KMyMoneyGeneralCombo::currentItem());
}
KMyMoneyOccurrencePeriodCombo::KMyMoneyOccurrencePeriodCombo(QWidget* parent) :
KMyMoneyOccurrenceCombo(parent)
{
- addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_ONCE).toLatin1()), MyMoneySchedule::OCCUR_ONCE);
- addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_DAILY).toLatin1()), MyMoneySchedule::OCCUR_DAILY);
- addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_WEEKLY).toLatin1()), MyMoneySchedule::OCCUR_WEEKLY);
- addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_EVERYHALFMONTH).toLatin1()), MyMoneySchedule::OCCUR_EVERYHALFMONTH);
- addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_MONTHLY).toLatin1()), MyMoneySchedule::OCCUR_MONTHLY);
- addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(MyMoneySchedule::OCCUR_YEARLY).toLatin1()), MyMoneySchedule::OCCUR_YEARLY);
+ addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Once).toLatin1()), (int)Schedule::Occurrence::Once);
+ addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Daily).toLatin1()), (int)Schedule::Occurrence::Daily);
+ addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Weekly).toLatin1()), (int)Schedule::Occurrence::Weekly);
+ addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryHalfMonth).toLatin1()), (int)Schedule::Occurrence::EveryHalfMonth);
+ addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Monthly).toLatin1()), (int)Schedule::Occurrence::Monthly);
+ addItem(i18nc("Schedule occurrence period", MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Yearly).toLatin1()), (int)Schedule::Occurrence::Yearly);
}
KMyMoneyFrequencyCombo::KMyMoneyFrequencyCombo(QWidget* parent) :
KMyMoneyOccurrenceCombo(parent)
{
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_ONCE).toLatin1()), MyMoneySchedule::OCCUR_ONCE);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_DAILY).toLatin1()), MyMoneySchedule::OCCUR_DAILY);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_WEEKLY).toLatin1()), MyMoneySchedule::OCCUR_WEEKLY);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYOTHERWEEK).toLatin1()), MyMoneySchedule::OCCUR_EVERYOTHERWEEK);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYHALFMONTH).toLatin1()), MyMoneySchedule::OCCUR_EVERYHALFMONTH);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYTHREEWEEKS).toLatin1()), MyMoneySchedule::OCCUR_EVERYTHREEWEEKS);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS).toLatin1()), MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYFOURWEEKS).toLatin1()), MyMoneySchedule::OCCUR_EVERYFOURWEEKS);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_MONTHLY).toLatin1()), MyMoneySchedule::OCCUR_MONTHLY);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS).toLatin1()), MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYOTHERMONTH).toLatin1()), MyMoneySchedule::OCCUR_EVERYOTHERMONTH);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYTHREEMONTHS).toLatin1()), MyMoneySchedule::OCCUR_EVERYTHREEMONTHS);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYFOURMONTHS).toLatin1()), MyMoneySchedule::OCCUR_EVERYFOURMONTHS);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_TWICEYEARLY).toLatin1()), MyMoneySchedule::OCCUR_TWICEYEARLY);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_YEARLY).toLatin1()), MyMoneySchedule::OCCUR_YEARLY);
- addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(MyMoneySchedule::OCCUR_EVERYOTHERYEAR).toLatin1()), MyMoneySchedule::OCCUR_EVERYOTHERYEAR);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Once).toLatin1()), (int)Schedule::Occurrence::Once);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Daily).toLatin1()), (int)Schedule::Occurrence::Daily);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Weekly).toLatin1()), (int)Schedule::Occurrence::Weekly);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherWeek).toLatin1()), (int)Schedule::Occurrence::EveryOtherWeek);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryHalfMonth).toLatin1()), (int)Schedule::Occurrence::EveryHalfMonth);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThreeWeeks).toLatin1()), (int)Schedule::Occurrence::EveryThreeWeeks);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThirtyDays).toLatin1()), (int)Schedule::Occurrence::EveryThirtyDays);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryFourWeeks).toLatin1()), (int)Schedule::Occurrence::EveryFourWeeks);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Monthly).toLatin1()), (int)Schedule::Occurrence::Monthly);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryEightWeeks).toLatin1()), (int)Schedule::Occurrence::EveryEightWeeks);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherMonth).toLatin1()), (int)Schedule::Occurrence::EveryOtherMonth);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThreeMonths).toLatin1()), (int)Schedule::Occurrence::EveryThreeMonths);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryFourMonths).toLatin1()), (int)Schedule::Occurrence::EveryFourMonths);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::TwiceYearly).toLatin1()), (int)Schedule::Occurrence::TwiceYearly);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Yearly).toLatin1()), (int)Schedule::Occurrence::Yearly);
+ addItem(i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherYear).toLatin1()), (int)Schedule::Occurrence::EveryOtherYear);
connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentDataChanged()));
}
int KMyMoneyFrequencyCombo::daysBetweenEvents() const
{
return MyMoneySchedule::daysBetweenEvents(currentItem());
}
int KMyMoneyFrequencyCombo::eventsPerYear() const
{
return MyMoneySchedule::eventsPerYear(currentItem());
}
QVariant KMyMoneyFrequencyCombo::currentData() const
{
return itemData(currentIndex(), Qt::UserRole);
}
void KMyMoneyFrequencyCombo::setCurrentData(QVariant data)
{
setItemData(currentIndex(), data, Qt::UserRole);
}
void KMyMoneyFrequencyCombo::slotCurrentDataChanged()
{
emit currentDataChanged(currentData());
}
#include "moc_kmymoneymvccombo.cpp"
diff --git a/kmymoney/widgets/kmymoneymvccombo.h b/kmymoney/widgets/kmymoneymvccombo.h
index 47b0bbca4..c860cc2a3 100644
--- a/kmymoney/widgets/kmymoneymvccombo.h
+++ b/kmymoney/widgets/kmymoneymvccombo.h
@@ -1,487 +1,487 @@
/***************************************************************************
kmymoneymvccombo.h - description
-------------------
begin : Mon Jan 09 2010
copyright : (C) 2010 by Thomas Baumgart <ipwizard@users.sourceforge.net>
Cristian Onet <cristian.onet@gmail.com>
Alvaro Soliverez <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 KMYMONEYMVCCOMBO_H
#define KMYMONEYMVCCOMBO_H
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
#include <KComboBox>
// ----------------------------------------------------------------------------
// Project Includes
#include "kmm_widgets_export.h"
#include "mymoneyaccount.h"
#include "mymoneypayee.h"
#include "mymoneytag.h"
#include "mymoneyschedule.h"
#include "register.h"
#include "mymoneytransactionfilter.h"
class QSortFilterProxyModel;
/**
* @author Cristian Onet
* This class will replace the KMyMoneyCombo class when all widgets will use the MVC
*/
class KMM_WIDGETS_EXPORT KMyMoneyMVCCombo : public KComboBox
{
Q_OBJECT
Q_PROPERTY(QString selectedItem READ selectedItem WRITE setSelectedItem STORED false)
public:
KMyMoneyMVCCombo(QWidget* parent = 0);
explicit KMyMoneyMVCCombo(bool editable, QWidget* parent = 0);
~KMyMoneyMVCCombo();
/**
* @sa KLineEdit::setPlaceholderText()
*/
void setPlaceholderText(const QString& hint) const;
/**
* This method returns the id of the first selected item.
*
* @return reference to QString containing the id. If no item
* is selected the QString will be empty.
*/
const QString& selectedItem() const;
/**
* This method selects the item with the respective @a id.
*
* @param id reference to QString containing the id
*/
void setSelectedItem(const QString& id);
/**
* Protect an entry from selection. Protection is controlled by
* the parameter @p protect.
*
* @param id id of item for which to modify the protection
* @param protect if true, the entry specified by @p accId cannot be
* selected. If false, it can be selected.
* Defaults to @p true.
*/
void protectItem(int id, bool protect);
/**
* Make the completion match on any substring or only
* from the start of an entry.
*
* @param enabled @a true turns on substring match, @a false turns it off.
* The default is @a false.
*/
void setSubstringSearch(bool enabled);
/**
* set setSubstringSearch(enabled) of all children of widget
*
* @param widget a valid pointer (not 0)
*/
static void setSubstringSearchForChildren(QWidget *const widget, bool enabled = false);
/**
* Reimplemented for internal reasons, no API change
*/
void setEditable(bool editable);
protected slots:
void activated(int index);
protected:
/**
* reimplemented to support detection of new items
*/
void focusOutEvent(QFocusEvent*);
/**
* check if the current text is contained in the internal list, if not ask the user if want to create a new item.
*/
virtual void checkCurrentText();
/**
* set the widgets text area based on the item with the given @a id.
*/
virtual void setCurrentTextById(const QString& id);
/**
* Overridden for internal reasons, no API change
*/
void connectNotify(const QMetaMethod & signal);
/**
* Overridden for internal reasons, no API change
*/
void disconnectNotify(const QMetaMethod & signal);
/**
* overridden for internal reasons, no API change
*/
void setCurrentText(const QString& txt = QString()) {
KComboBox::setItemText(KComboBox::currentIndex(), txt);
}
signals:
void itemSelected(const QString& id);
void objectCreation(bool);
void createItem(const QString&, QString&);
void lostFocus();
private:
/// \internal d-pointer class.
class Private;
/// \internal d-pointer instance.
Private* const d;
/**
* This is just a cache to be able to implement the old interface.
*/
mutable QString m_id;
};
/**
* This class implements a text based payee selector.
* When initially used, the widget has the functionality of a KComboBox object.
* Whenever a key is pressed, the set of loaded payees is searched for
* payees names which match the currently entered text.
*
* If any match is found a list selection box is opened and the user can use
* the up/down, page-up/page-down keys or the mouse to navigate in the list. If
* a payee is selected, the selection box is closed. Other key-strokes are
* directed to the parent object to manipulate the text. The visible contents of
* the selection box is updated with every key-stroke.
*
* This object is a replacement of the kMyMoneyPayee object and should be used
* for new code.
*
* @author Thomas Baumgart
*/
class KMM_WIDGETS_EXPORT KMyMoneyPayeeCombo : public KMyMoneyMVCCombo
{
Q_OBJECT
public:
KMyMoneyPayeeCombo(QWidget* parent = 0);
void loadPayees(const QList<MyMoneyPayee>& list);
};
/**
* This class implements a text based tag selector.
* The widget has the functionality of a KMyMoneyPayeeCombo object.
* Whenever a key is pressed, the set of loaded tags is searched for
* tags names which match the currently entered text.
*
* @author Alessandro Russo
*/
class KMM_WIDGETS_EXPORT KMyMoneyTagCombo : public KMyMoneyMVCCombo
{
Q_OBJECT
public:
KMyMoneyTagCombo(QWidget* parent = 0);
void loadTags(const QList<MyMoneyTag>& list);
/** ids in usedIdList are escluded from the internal list
* you should call loadTags before calling setUsedTagList because it doesn't readd
* tag removed in previous call*/
void setUsedTagList(QList<QString>& usedIdList, QList<QString>& usedTagNameList);
protected:
/**
* check if the current text is contained in the internal list, if not ask the user if want to create a new item.
*/
virtual void checkCurrentText();
private:
QList<QString> m_usedIdList;
QList<QString> m_usedTagNameList;
QList<QString> m_closedIdList;
QList<QString> m_closedTagNameList;
};
/**
* This class implements a tag label. It create a QFrame and inside it a QToolButton
* with a 'X' Icon and a QLabel with the name of the Tag
*
* @author Alessandro Russo
*/
class KTagLabel : public QFrame
{
Q_OBJECT
public:
KTagLabel(const QString& id, const QString& name, QWidget* parent = 0);
signals:
void clicked(bool);
private:
QString m_tagId;
};
/**
* This widget contain a KMyMoneyTagCombo widget and 0 or more KTagLabel widgets
* call KMyMoneyTagCombo.loadTags with the correct list whenever a new KTagLabel is created or
* deleted by removing or adding the relative tag
*
* @author Alessandro Russo
*/
class KMM_WIDGETS_EXPORT KTagContainer : public QWidget
{
Q_OBJECT
public:
KTagContainer(QWidget* parent = 0);
void loadTags(const QList<MyMoneyTag>& list);
KMyMoneyTagCombo* tagCombo() {
return m_tagCombo;
}
const QList<QString> selectedTags();
void addTagWidget(const QString& id);
void RemoveAllTagWidgets();
protected slots:
void slotRemoveTagWidget();
void slotAddTagWidget();
private:
KMyMoneyTagCombo *m_tagCombo;
QList<KTagLabel*> m_tagLabelList;
QList<QString> m_tagIdList;
QList<QString> m_tagNameList;
// A local cache of the list of all Tags, it's updated when loadTags is called
QList<MyMoneyTag> m_list;
};
/**
* @author Thomas Baumgart
* This class implements a combo box with the possible states for
* reconciliation.
*/
class KMM_WIDGETS_EXPORT KMyMoneyReconcileCombo : public KMyMoneyMVCCombo
{
Q_OBJECT
public:
KMyMoneyReconcileCombo(QWidget *w = 0);
void setState(MyMoneySplit::reconcileFlagE state);
MyMoneySplit::reconcileFlagE state() const;
void removeDontCare();
protected slots:
void slotSetState(const QString&);
};
/**
* @author Thomas Baumgart
* This class implements a combo box with the possible states for
* actions (Deposit, Withdrawal, etc.).
*/
class KMM_WIDGETS_EXPORT KMyMoneyCashFlowCombo : public KMyMoneyMVCCombo
{
Q_OBJECT
public:
/**
* Create a combo box that contains the entries "Pay to", "From" and
* " " for don't care.
*/
- explicit KMyMoneyCashFlowCombo(QWidget *w = 0, MyMoneyAccount::accountTypeE type = MyMoneyAccount::Asset);
+ explicit KMyMoneyCashFlowCombo(QWidget *w = 0, eMyMoney::Account type = eMyMoney::Account::Asset);
void setDirection(KMyMoneyRegister::CashFlowDirection dir);
KMyMoneyRegister::CashFlowDirection direction() const {
return m_dir;
}
void removeDontCare();
protected slots:
void slotSetDirection(const QString& id);
signals:
void directionSelected(KMyMoneyRegister::CashFlowDirection);
private:
KMyMoneyRegister::CashFlowDirection m_dir;
};
/**
* @author Thomas Baumgart
* This class implements a combo box with the possible activities
* for investment transactions (buy, sell, dividend, etc.)
*/
class KMM_WIDGETS_EXPORT KMyMoneyActivityCombo : public KMyMoneyMVCCombo
{
Q_OBJECT
public:
/**
* Create a combo box that contains the entries "Buy", "Sell" etc.
*/
KMyMoneyActivityCombo(QWidget *w = 0);
void setActivity(MyMoneySplit::investTransactionTypeE activity);
MyMoneySplit::investTransactionTypeE activity() const {
return m_activity;
}
protected slots:
void slotSetActivity(const QString& id);
signals:
void activitySelected(MyMoneySplit::investTransactionTypeE);
private:
MyMoneySplit::investTransactionTypeE m_activity;
};
class KMM_WIDGETS_EXPORT KMyMoneyGeneralCombo : public KComboBox
{
Q_OBJECT
Q_PROPERTY(int currentItem READ currentItem WRITE setCurrentItem STORED false)
public:
KMyMoneyGeneralCombo(QWidget* parent = 0);
virtual ~KMyMoneyGeneralCombo();
void insertItem(const QString& txt, int id, int idx = -1);
void setCurrentItem(int id);
int currentItem() const;
void removeItem(int id);
public slots:
void clear();
signals:
void itemSelected(int id);
protected:
// prevent the caller to use the standard KComboBox insertItem function with a default idx
void insertItem(const QString&);
protected slots:
void slotChangeItem(int idx);
};
/**
* This class implements a time period selector
* @author Thomas Baumgart
*/
class KMM_WIDGETS_EXPORT KMyMoneyPeriodCombo : public KMyMoneyGeneralCombo
{
Q_OBJECT
public:
KMyMoneyPeriodCombo(QWidget* parent = 0);
MyMoneyTransactionFilter::dateOptionE currentItem() const;
void setCurrentItem(MyMoneyTransactionFilter::dateOptionE id);
/**
* This function returns the actual start date for the given
* period definition given by @p id. For user defined periods
* the returned value is QDate()
*/
static QDate start(MyMoneyTransactionFilter::dateOptionE id);
/**
* This function returns the actual end date for the given
* period definition given by @p id. For user defined periods
* the returned value is QDate()
*/
static QDate end(MyMoneyTransactionFilter::dateOptionE id);
// static void dates(QDate& start, QDate& end, MyMoneyTransactionFilter::dateOptionE id);
};
/**
* This class implements an occurrence selector
* as a parent class for both OccurrencePeriod and Frequency combos
*
* @author Colin Wright
*/
class KMM_WIDGETS_EXPORT KMyMoneyOccurrenceCombo : public KMyMoneyGeneralCombo
{
Q_OBJECT
public:
KMyMoneyOccurrenceCombo(QWidget* parent = 0);
- MyMoneySchedule::occurrenceE currentItem() const;
+ eMyMoney::Schedule::Occurrence currentItem() const;
};
/**
* This class implements an occurrence period selector
*
* @author Colin Wright
*/
class KMM_WIDGETS_EXPORT KMyMoneyOccurrencePeriodCombo : public KMyMoneyOccurrenceCombo
{
Q_OBJECT
public:
KMyMoneyOccurrencePeriodCombo(QWidget* parent = 0);
};
/**
* This class implements a payment frequency selector
* @author Thomas Baumgart
*/
class KMM_WIDGETS_EXPORT KMyMoneyFrequencyCombo : public KMyMoneyOccurrenceCombo
{
Q_OBJECT
Q_PROPERTY(QVariant data READ currentData WRITE setCurrentData STORED false)
public:
KMyMoneyFrequencyCombo(QWidget* parent = 0);
/**
* This method returns the number of events for the selected payment
* frequency (eg for yearly the return value is 1 and for monthly it
* is 12). In case, the frequency cannot be converted (once, every other year, etc.)
* the method returns 0.
*/
int eventsPerYear() const;
/**
* This method returns the number of days between two events of
* the selected frequency. The return value for months is based
* on 30 days and the year is 360 days long.
*/
int daysBetweenEvents() const;
QVariant currentData() const;
void setCurrentData(QVariant data);
Q_SIGNALS:
void currentDataChanged(QVariant data);
protected slots:
void slotCurrentDataChanged();
private:
QVariant data;
};
#endif
diff --git a/kmymoney/widgets/register.cpp b/kmymoney/widgets/register.cpp
index f8aa83302..cc2bf3710 100644
--- a/kmymoney/widgets/register.cpp
+++ b/kmymoney/widgets/register.cpp
@@ -1,2127 +1,2129 @@
/***************************************************************************
register.cpp - description
-------------------
begin : Fri Mar 10 2006
copyright : (C) 2006 by Thomas Baumgart
email : Thomas Baumgart <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 <config-kmymoney.h>
#include "register.h"
#include <typeinfo>
// ----------------------------------------------------------------------------
// QT Includes
#include <QString>
#include <QToolTip>
#include <QPixmap>
#include <QMouseEvent>
#include <QList>
#include <QKeyEvent>
#include <QEvent>
#include <QFrame>
#include <QHeaderView>
#include <QStyleOptionViewItem>
#include <QApplication>
#include <QPushButton>
#include <QPainter>
#include <QTimer>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "stdtransactiondownloaded.h"
#include "stdtransactionmatched.h"
#include "scheduledtransaction.h"
#include "kmymoneyglobalsettings.h"
#include "mymoneyfile.h"
+#include "mymoneyenums.h"
static const char * sortOrderText[] = {
I18N_NOOP2("Unknown sort order", "Unknown"),
I18N_NOOP("Post date"),
I18N_NOOP("Date entered"),
I18N_NOOP("Payee"),
I18N_NOOP("Amount"),
I18N_NOOP("Number"),
I18N_NOOP("Entry order"),
I18N_NOOP("Type"),
I18N_NOOP("Category"),
I18N_NOOP("Reconcile state"),
I18N_NOOP("Security")
// add new values above this comment line
};
using namespace KMyMoneyRegister;
+using namespace eMyMoney;
static unsigned char fancymarker_bg_image[] = {
/* 200x49 */
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00, 0x31,
0x08, 0x06, 0x00, 0x00, 0x00, 0x9F, 0xC5, 0xE6,
0x4F, 0x00, 0x00, 0x00, 0x06, 0x62, 0x4B, 0x47,
0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0,
0xBD, 0xA7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70,
0x48, 0x59, 0x73, 0x00, 0x00, 0x0B, 0x13, 0x00,
0x00, 0x0B, 0x13, 0x01, 0x00, 0x9A, 0x9C, 0x18,
0x00, 0x00, 0x00, 0x86, 0x49, 0x44, 0x41, 0x54,
0x78, 0xDA, 0xED, 0xDD, 0x31, 0x0A, 0x84, 0x40,
0x10, 0x44, 0xD1, 0x1A, 0x19, 0x10, 0xCF, 0xE6,
0xFD, 0x4F, 0xB2, 0x88, 0x08, 0x22, 0x9B, 0x18,
0x4E, 0x1B, 0x2C, 0x1B, 0x18, 0xBC, 0x07, 0x7D,
0x81, 0x82, 0x1F, 0x77, 0x4B, 0xB2, 0x06, 0x18,
0xEA, 0x49, 0x3E, 0x66, 0x00, 0x81, 0x80, 0x40,
0xE0, 0xDF, 0x81, 0x6C, 0x66, 0x80, 0x3A, 0x90,
0xDD, 0x0C, 0x50, 0x07, 0x72, 0x98, 0x01, 0xEA,
0x40, 0x4E, 0x33, 0x40, 0x1D, 0xC8, 0x65, 0x06,
0x18, 0x6B, 0xF7, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x16, 0x3E,
0x4C, 0xC1, 0x83, 0x9E, 0x64, 0x32, 0x03, 0x08,
0x04, 0x7E, 0x0A, 0xA4, 0x9B, 0x01, 0xEA, 0x40,
0x66, 0x33, 0x40, 0x1D, 0xC8, 0x62, 0x06, 0x18,
0xFB, 0x02, 0x05, 0x87, 0x08, 0x55, 0xFE, 0xDE,
0xA2, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
};
QPixmap* GroupMarker::m_bg = 0;
int GroupMarker::m_bgRefCnt = 0;
void ItemPtrVector::sort()
{
if (count() > 0) {
// get rid of 0 pointers in the list
KMyMoneyRegister::ItemPtrVector::iterator it_l;
RegisterItem *item;
for (it_l = begin(); it_l != end(); ++it_l) {
if (*it_l == 0) {
item = last();
*it_l = item;
pop_back();
--it_l;
}
}
std::sort(begin(), end(), item_cmp);
}
}
bool ItemPtrVector::item_cmp(RegisterItem* i1, RegisterItem* i2)
{
const QList<TransactionSortField>& sortOrder = i1->parent()->sortOrder();
QList<TransactionSortField>::const_iterator it;
int rc = 0;
bool ok1, ok2;
qulonglong n1, n2;
for (it = sortOrder.begin(); it != sortOrder.end(); ++it) {
TransactionSortField sortField = static_cast<TransactionSortField>(*it);
switch (qAbs(static_cast<int>(sortField))) {
case PostDateSort:
rc = i2->sortPostDate().daysTo(i1->sortPostDate());
break;
case EntryDateSort:
rc = i2->sortEntryDate().daysTo(i1->sortEntryDate());
break;
case PayeeSort:
rc = QString::localeAwareCompare(i1->sortPayee(), i2->sortPayee());
break;
case ValueSort:
if (i1->sortValue() == i2->sortValue())
rc = 0;
else if (i1->sortValue() < i2->sortValue())
rc = -1;
else
rc = 1;
break;
case NoSort:
// convert both values to numbers
n1 = i1->sortNumber().toULongLong(&ok1);
n2 = i2->sortNumber().toULongLong(&ok2);
// the following four cases exist:
// a) both are converted correct
// compare them directly
// b) n1 is numeric, n2 is not
// numbers come first, so return -1
// c) n1 is not numeric, n2 is
// numbers come first, so return 1
// d) both are non numbers
// compare using localeAwareCompare
if (ok1 && ok2) { // case a)
rc = (n1 > n2) ? 1 : ((n1 == n2) ? 0 : -1);
} else if (ok1 && !ok2) {
rc = -1;
} else if (!ok1 && ok2) {
rc = 1;
} else
rc = QString::localeAwareCompare(i1->sortNumber(), i2->sortNumber());
break;
case EntryOrderSort:
rc = qstrcmp(i1->sortEntryOrder().toLatin1(), i2->sortEntryOrder().toLatin1());
break;
case TypeSort:
rc = i1->sortType() - i2->sortType();
break;
case CategorySort:
rc = QString::localeAwareCompare(i1->sortCategory(), i2->sortCategory());
break;
case ReconcileStateSort:
rc = static_cast<int>(i1->sortReconcileState()) - static_cast<int>(i2->sortReconcileState());
break;
case SecuritySort:
rc = QString::localeAwareCompare(i1->sortSecurity(), i2->sortSecurity());
break;
default:
qDebug("Invalid sort key %d", *it);
break;
}
// take care of group markers, but only first sort item
if ((rc == 0) && (it == sortOrder.begin())) {
rc = i1->sortSamePostDate() - i2->sortSamePostDate();
if (rc) {
return rc < 0;
}
}
// the items differ for this sort key so we can return a result
if (rc != 0) {
return (*it < 0) ? rc >= 0 : rc < 0;
}
}
if (rc == 0) {
rc = qstrcmp(i1->sortEntryOrder().toLatin1(), i2->sortEntryOrder().toLatin1());
}
return rc < 0;
}
GroupMarker::GroupMarker(Register *parent, const QString& txt) :
RegisterItem(parent),
m_txt(txt),
m_showDate(false),
m_erroneous(false)
{
int h;
if (m_parent) {
h = m_parent->rowHeightHint();
} else {
QFontMetrics fm(KMyMoneyGlobalSettings::listCellFont());
h = fm.lineSpacing() + 6;
}
if (m_bg && (m_bg->height() != h)) {
delete m_bg;
m_bg = 0;
}
// convert the backgroud once
if (m_bg == 0) {
m_bg = new QPixmap;
m_bg->loadFromData(fancymarker_bg_image, sizeof(fancymarker_bg_image));
*m_bg = m_bg->scaled(m_bg->width(), h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
++m_bgRefCnt;
}
GroupMarker::~GroupMarker()
{
--m_bgRefCnt;
if (!m_bgRefCnt) {
delete m_bg;
m_bg = 0;
}
}
void GroupMarker::paintRegisterCell(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index)
{
QRect r(option.rect);
painter->save();
// the group marker always uses all cols
r.setX(m_parent->horizontalHeader()->sectionPosition(0));
r.setWidth(m_parent->viewport()->width());
painter->translate(r.x(), r.y());
QRect cellRect;
cellRect.setX(0);
cellRect.setY(0);
cellRect.setWidth(m_parent->viewport()->width());
cellRect.setHeight(m_parent->rowHeight(index.row()));
option.palette.setColor(QPalette::Base, isErroneous() ? KMyMoneyGlobalSettings::schemeColor(SchemeColor::TransactionErroneous) : KMyMoneyGlobalSettings::schemeColor(SchemeColor::GroupMarker));
QBrush backgroundBrush(option.palette.color(QPalette::Base));
painter->fillRect(cellRect, backgroundBrush);
painter->setPen(KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListGrid));
painter->drawLine(cellRect.x(), cellRect.height() - 1, cellRect.width(), cellRect.height() - 1);
// now write the text
painter->setPen(option.palette.color(isErroneous() ? QPalette::HighlightedText : QPalette::Text));
QFont font = painter->font();
font.setBold(true);
painter->setFont(font);
painter->drawText(cellRect, Qt::AlignVCenter | Qt::AlignCenter, m_txt);
cellRect.setHeight(m_bg->height());
// now it's time to draw the background
painter->drawPixmap(cellRect, *m_bg);
// in case we need to show the date, we just paint it in col 1
if (m_showDate) {
font.setBold(false);
cellRect.setX(m_parent->horizontalHeader()->sectionPosition(DateColumn));
cellRect.setWidth(m_parent->horizontalHeader()->sectionSize(DateColumn));
painter->setFont(font);
painter->drawText(cellRect, Qt::AlignVCenter | Qt::AlignCenter, QLocale().toString(sortPostDate(), QLocale::ShortFormat));
}
painter->restore();
}
void GroupMarker::paintFormCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
{
Q_UNUSED(painter);
Q_UNUSED(option);
Q_UNUSED(index);
}
int GroupMarker::rowHeightHint() const
{
if (!m_visible)
return 0;
return m_bg->height();
}
StatementGroupMarker::StatementGroupMarker(Register* parent, CashFlowDirection dir, const QDate& date, const QString& txt) :
FancyDateGroupMarker(parent, date, txt),
m_dir(dir)
{
m_showDate = true;
}
FancyDateGroupMarker::FancyDateGroupMarker(Register* parent, const QDate& date, const QString& txt) :
GroupMarker(parent, txt),
m_date(date)
{
}
FiscalYearGroupMarker::FiscalYearGroupMarker(Register* parent, const QDate& date, const QString& txt) :
FancyDateGroupMarker(parent, date, txt)
{
}
SimpleDateGroupMarker::SimpleDateGroupMarker(Register* parent, const QDate& date, const QString& txt) :
FancyDateGroupMarker(parent, date, txt)
{
}
int SimpleDateGroupMarker::rowHeightHint() const
{
if (!m_visible)
return 0;
return RegisterItem::rowHeightHint() / 2;
}
void SimpleDateGroupMarker::paintRegisterCell(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index)
{
QRect cellRect = option.rect;
painter->save();
cellRect.setWidth(m_parent->viewport()->width());
cellRect.setHeight(m_parent->rowHeight(index.row() + m_startRow));
if (m_alternate)
option.palette.setColor(QPalette::Base, KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListBackground2));
else
option.palette.setColor(QPalette::Base, KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListBackground1));
QBrush backgroundBrush(option.palette.color(QPalette::Base));
backgroundBrush.setStyle(Qt::Dense5Pattern);
backgroundBrush.setColor(KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListGrid));
painter->eraseRect(cellRect);
painter->fillRect(cellRect, backgroundBrush);
painter->setPen(KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListGrid));
painter->restore();
}
-TypeGroupMarker::TypeGroupMarker(Register* parent, CashFlowDirection dir, MyMoneyAccount::accountTypeE accType) :
+TypeGroupMarker::TypeGroupMarker(Register* parent, CashFlowDirection dir, Account accType) :
GroupMarker(parent),
m_dir(dir)
{
switch (dir) {
case Deposit:
m_txt = i18nc("Deposits onto account", "Deposits");
- if (accType == MyMoneyAccount::CreditCard) {
+ if (accType == Account::CreditCard) {
m_txt = i18nc("Payments towards credit card", "Payments");
}
break;
case Payment:
m_txt = i18nc("Payments made from account", "Payments");
- if (accType == MyMoneyAccount::CreditCard) {
+ if (accType == Account::CreditCard) {
m_txt = i18nc("Payments made with credit card", "Charges");
}
break;
default:
qDebug("Unknown CashFlowDirection %d for TypeGroupMarker constructor", dir);
break;
}
}
PayeeGroupMarker::PayeeGroupMarker(Register* parent, const QString& name) :
GroupMarker(parent, name)
{
}
CategoryGroupMarker::CategoryGroupMarker(Register* parent, const QString& category) :
GroupMarker(parent, category)
{
}
ReconcileGroupMarker::ReconcileGroupMarker(Register* parent, MyMoneySplit::reconcileFlagE state) :
GroupMarker(parent),
m_state(state)
{
switch (state) {
case MyMoneySplit::NotReconciled:
m_txt = i18nc("Reconcile state 'Not reconciled'", "Not reconciled");
break;
case MyMoneySplit::Cleared:
m_txt = i18nc("Reconcile state 'Cleared'", "Cleared");
break;
case MyMoneySplit::Reconciled:
m_txt = i18nc("Reconcile state 'Reconciled'", "Reconciled");
break;
case MyMoneySplit::Frozen:
m_txt = i18nc("Reconcile state 'Frozen'", "Frozen");
break;
default:
m_txt = i18nc("Unknown reconcile state", "Unknown");
break;
}
}
RegisterItemDelegate::RegisterItemDelegate(Register *parent) : QStyledItemDelegate(parent), m_register(parent)
{
}
RegisterItemDelegate::~RegisterItemDelegate()
{
}
void RegisterItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
RegisterItem* const item = m_register->itemAtRow(index.row());
if (item && m_register->updatesEnabled()) {
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
item->paintRegisterCell(painter, opt, index);
}
}
Register::Register(QWidget *parent) :
TransactionEditorContainer(parent),
m_selectAnchor(0),
m_focusItem(0),
m_firstItem(0),
m_lastItem(0),
m_firstErroneous(0),
m_lastErroneous(0),
m_rowHeightHint(0),
m_ledgerLensForced(false),
m_selectionMode(QTableWidget::MultiSelection),
m_needResize(true),
m_listsDirty(false),
m_ignoreNextButtonRelease(false),
m_needInitialColumnResize(false),
m_usedWithEditor(false),
m_mouseButton(Qt::MouseButtons(Qt::NoButton)),
m_modifiers(Qt::KeyboardModifiers(Qt::NoModifier)),
m_detailsColumnType(PayeeFirst)
{
// used for custom coloring with the help of the application's stylesheet
setObjectName(QLatin1String("register"));
setItemDelegate(new RegisterItemDelegate(this));
setEditTriggers(QAbstractItemView::NoEditTriggers);
setColumnCount(MaxColumns);
setSelectionBehavior(QAbstractItemView::SelectRows);
setAcceptDrops(true);
setShowGrid(false);
setContextMenuPolicy(Qt::DefaultContextMenu);
setHorizontalHeaderItem(NumberColumn, new QTableWidgetItem());
setHorizontalHeaderItem(DateColumn, new QTableWidgetItem());
setHorizontalHeaderItem(AccountColumn, new QTableWidgetItem());
setHorizontalHeaderItem(SecurityColumn, new QTableWidgetItem());
setHorizontalHeaderItem(DetailColumn, new QTableWidgetItem());
setHorizontalHeaderItem(ReconcileFlagColumn, new QTableWidgetItem());
setHorizontalHeaderItem(PaymentColumn, new QTableWidgetItem());
setHorizontalHeaderItem(DepositColumn, new QTableWidgetItem());
setHorizontalHeaderItem(QuantityColumn, new QTableWidgetItem());
setHorizontalHeaderItem(PriceColumn, new QTableWidgetItem());
setHorizontalHeaderItem(ValueColumn, new QTableWidgetItem());
setHorizontalHeaderItem(BalanceColumn, new QTableWidgetItem());
// keep the following list in sync with KMyMoneyRegister::Column in transaction.h
horizontalHeaderItem(NumberColumn)->setText(i18nc("Cheque Number", "No."));
horizontalHeaderItem(DateColumn)->setText(i18n("Date"));
horizontalHeaderItem(AccountColumn)->setText(i18n("Account"));
horizontalHeaderItem(SecurityColumn)->setText(i18n("Security"));
horizontalHeaderItem(DetailColumn)->setText(i18n("Details"));
horizontalHeaderItem(ReconcileFlagColumn)->setText(i18n("C"));
horizontalHeaderItem(PaymentColumn)->setText(i18n("Payment"));
horizontalHeaderItem(DepositColumn)->setText(i18n("Deposit"));
horizontalHeaderItem(QuantityColumn)->setText(i18n("Quantity"));
horizontalHeaderItem(PriceColumn)->setText(i18n("Price"));
horizontalHeaderItem(ValueColumn)->setText(i18n("Value"));
horizontalHeaderItem(BalanceColumn)->setText(i18n("Balance"));
verticalHeader()->hide();
horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
horizontalHeader()->setSortIndicatorShown(false);
horizontalHeader()->setSectionsMovable(false);
horizontalHeader()->setSectionsClickable(false);
horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(cellClicked(int,int)), this, SLOT(selectItem(int,int)));
connect(this, SIGNAL(cellDoubleClicked(int,int)), this, SLOT(slotDoubleClicked(int,int)));
}
Register::~Register()
{
clear();
}
bool Register::eventFilter(QObject* o, QEvent* e)
{
if (o == this && e->type() == QEvent::KeyPress) {
QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
if (ke->key() == Qt::Key_Menu) {
emit openContextMenu();
return true;
}
}
return QTableWidget::eventFilter(o, e);
}
void Register::setupRegister(const MyMoneyAccount& account, const QList<Column>& cols)
{
m_account = account;
setUpdatesEnabled(false);
for (int i = 0; i < MaxColumns; ++i)
hideColumn(i);
m_needInitialColumnResize = true;
m_lastCol = static_cast<Column>(0);
QList<Column>::const_iterator it_c;
for (it_c = cols.begin(); it_c != cols.end(); ++it_c) {
if ((*it_c) > MaxColumns)
continue;
showColumn(*it_c);
if (*it_c > m_lastCol)
m_lastCol = *it_c;
}
setUpdatesEnabled(true);
}
void Register::setupRegister(const MyMoneyAccount& account, bool showAccountColumn)
{
m_account = account;
setUpdatesEnabled(false);
for (int i = 0; i < MaxColumns; ++i)
hideColumn(i);
horizontalHeaderItem(PaymentColumn)->setText(i18nc("Payment made from account", "Payment"));
horizontalHeaderItem(DepositColumn)->setText(i18nc("Deposit into account", "Deposit"));
if (account.id().isEmpty()) {
setUpdatesEnabled(true);
return;
}
m_needInitialColumnResize = true;
// turn on standard columns
showColumn(DateColumn);
showColumn(DetailColumn);
showColumn(ReconcileFlagColumn);
// balance
switch (account.accountType()) {
- case MyMoneyAccount::Stock:
+ case Account::Stock:
break;
default:
showColumn(BalanceColumn);
break;
}
// Number column
switch (account.accountType()) {
- case MyMoneyAccount::Savings:
- case MyMoneyAccount::Cash:
- case MyMoneyAccount::Loan:
- case MyMoneyAccount::AssetLoan:
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Equity:
+ case Account::Savings:
+ case Account::Cash:
+ case Account::Loan:
+ case Account::AssetLoan:
+ case Account::Asset:
+ case Account::Liability:
+ case Account::Equity:
if (KMyMoneyGlobalSettings::alwaysShowNrField())
showColumn(NumberColumn);
break;
- case MyMoneyAccount::Checkings:
- case MyMoneyAccount::CreditCard:
+ case Account::Checkings:
+ case Account::CreditCard:
showColumn(NumberColumn);
break;
default:
hideColumn(NumberColumn);
break;
}
switch (account.accountType()) {
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
+ case Account::Income:
+ case Account::Expense:
showAccountColumn = true;
break;
default:
break;
}
if (showAccountColumn)
showColumn(AccountColumn);
// Security, activity, payment, deposit, amount, price and value column
switch (account.accountType()) {
default:
showColumn(PaymentColumn);
showColumn(DepositColumn);
break;
- case MyMoneyAccount::Investment:
+ case Account::Investment:
showColumn(SecurityColumn);
showColumn(QuantityColumn);
showColumn(PriceColumn);
showColumn(ValueColumn);
break;
}
// headings
switch (account.accountType()) {
- case MyMoneyAccount::CreditCard:
+ case Account::CreditCard:
horizontalHeaderItem(PaymentColumn)->setText(i18nc("Payment made with credit card", "Charge"));
horizontalHeaderItem(DepositColumn)->setText(i18nc("Payment towards credit card", "Payment"));
break;
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::AssetLoan:
+ case Account::Asset:
+ case Account::AssetLoan:
horizontalHeaderItem(PaymentColumn)->setText(i18nc("Decrease of asset/liability value", "Decrease"));
horizontalHeaderItem(DepositColumn)->setText(i18nc("Increase of asset/liability value", "Increase"));
break;
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Loan:
+ case Account::Liability:
+ case Account::Loan:
horizontalHeaderItem(PaymentColumn)->setText(i18nc("Increase of asset/liability value", "Increase"));
horizontalHeaderItem(DepositColumn)->setText(i18nc("Decrease of asset/liability value", "Decrease"));
break;
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
+ case Account::Income:
+ case Account::Expense:
horizontalHeaderItem(PaymentColumn)->setText(i18n("Income"));
horizontalHeaderItem(DepositColumn)->setText(i18n("Expense"));
break;
default:
break;
}
m_lastCol = BalanceColumn;
setUpdatesEnabled(true);
}
bool Register::focusNextPrevChild(bool next)
{
return QFrame::focusNextPrevChild(next);
}
void Register::setSortOrder(const QString& order)
{
const QStringList orderList = order.split(',', QString::SkipEmptyParts);
QStringList::const_iterator it;
m_sortOrder.clear();
for (it = orderList.constBegin(); it != orderList.constEnd(); ++it) {
m_sortOrder << static_cast<TransactionSortField>((*it).toInt());
}
}
void Register::sortItems()
{
if (m_items.count() == 0)
return;
// sort the array of pointers to the transactions
m_items.sort();
// update the next/prev item chains
RegisterItem* prev = 0;
RegisterItem* item;
m_firstItem = m_lastItem = 0;
for (QVector<RegisterItem*>::size_type i = 0; i < m_items.size(); ++i) {
item = m_items[i];
if (!item)
continue;
if (!m_firstItem)
m_firstItem = item;
m_lastItem = item;
if (prev)
prev->setNextItem(item);
item->setPrevItem(prev);
item->setNextItem(0);
prev = item;
}
// update the balance visibility settings
item = m_lastItem;
bool showBalance = true;
while (item) {
Transaction* t = dynamic_cast<Transaction*>(item);
if (t) {
t->setShowBalance(showBalance);
if (!t->isVisible()) {
showBalance = false;
}
}
item = item->prevItem();
}
// force update of the item index (row to item array)
m_listsDirty = true;
}
TransactionSortField Register::primarySortKey() const
{
if (!m_sortOrder.isEmpty())
return static_cast<KMyMoneyRegister::TransactionSortField>(m_sortOrder.first());
return UnknownSort;
}
void Register::clear()
{
m_firstErroneous = m_lastErroneous = 0;
m_ensureVisibleItem = 0;
m_items.clear();
RegisterItem* p;
while ((p = firstItem()) != 0) {
delete p;
}
m_firstItem = m_lastItem = 0;
m_listsDirty = true;
m_selectAnchor = 0;
m_focusItem = 0;
#ifndef KMM_DESIGNER
// recalculate row height hint
QFontMetrics fm(KMyMoneyGlobalSettings::listCellFont());
m_rowHeightHint = fm.lineSpacing() + 6;
#endif
m_needInitialColumnResize = true;
m_needResize = true;
updateRegister(true);
}
void Register::insertItemAfter(RegisterItem*p, RegisterItem* prev)
{
RegisterItem* next = 0;
if (!prev)
prev = lastItem();
if (prev) {
next = prev->nextItem();
prev->setNextItem(p);
}
if (next)
next->setPrevItem(p);
p->setPrevItem(prev);
p->setNextItem(next);
if (!m_firstItem)
m_firstItem = p;
if (!m_lastItem)
m_lastItem = p;
if (prev == m_lastItem)
m_lastItem = p;
m_listsDirty = true;
m_needResize = true;
}
void Register::addItem(RegisterItem* p)
{
RegisterItem* q = lastItem();
if (q)
q->setNextItem(p);
p->setPrevItem(q);
p->setNextItem(0);
m_items.append(p);
if (!m_firstItem)
m_firstItem = p;
m_lastItem = p;
m_listsDirty = true;
m_needResize = true;
}
void Register::removeItem(RegisterItem* p)
{
// remove item from list
if (p->prevItem())
p->prevItem()->setNextItem(p->nextItem());
if (p->nextItem())
p->nextItem()->setPrevItem(p->prevItem());
// update first and last pointer if required
if (p == m_firstItem)
m_firstItem = p->nextItem();
if (p == m_lastItem)
m_lastItem = p->prevItem();
// make sure we don't do it twice
p->setNextItem(0);
p->setPrevItem(0);
// remove it from the m_items array
int i = m_items.indexOf(p);
if (-1 != i) {
m_items[i] = 0;
}
m_listsDirty = true;
m_needResize = true;
}
RegisterItem* Register::firstItem() const
{
return m_firstItem;
}
RegisterItem* Register::nextItem(RegisterItem* item) const
{
return item->nextItem();
}
RegisterItem* Register::lastItem() const
{
return m_lastItem;
}
void Register::setupItemIndex(int rowCount)
{
// setup index array
m_itemIndex.clear();
m_itemIndex.reserve(rowCount);
// fill index array
rowCount = 0;
RegisterItem* prev = 0;
m_firstItem = m_lastItem = 0;
for (QVector<RegisterItem*>::size_type i = 0; i < m_items.size(); ++i) {
RegisterItem* item = m_items[i];
if (!item)
continue;
if (!m_firstItem)
m_firstItem = item;
m_lastItem = item;
if (prev)
prev->setNextItem(item);
item->setPrevItem(prev);
item->setNextItem(0);
prev = item;
for (int j = item->numRowsRegister(); j; --j) {
m_itemIndex.push_back(item);
}
}
}
void Register::updateAlternate() const
{
bool alternate = false;
for (QVector<RegisterItem*>::size_type i = 0; i < m_items.size(); ++i) {
RegisterItem* item = m_items[i];
if (!item)
continue;
if (item->isVisible()) {
item->setAlternate(alternate);
alternate ^= true;
}
}
}
void Register::suppressAdjacentMarkers()
{
bool lastWasGroupMarker = false;
KMyMoneyRegister::RegisterItem* p = lastItem();
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
if (t && t->transaction().id().isEmpty()) {
lastWasGroupMarker = true;
p = p->prevItem();
}
while (p) {
KMyMoneyRegister::GroupMarker* m = dynamic_cast<KMyMoneyRegister::GroupMarker*>(p);
if (m) {
// make adjacent group marker invisible except those that show statement information
if (lastWasGroupMarker && (dynamic_cast<KMyMoneyRegister::StatementGroupMarker*>(m) == 0)) {
m->setVisible(false);
}
lastWasGroupMarker = true;
} else if (p->isVisible())
lastWasGroupMarker = false;
p = p->prevItem();
}
}
void Register::updateRegister(bool forceUpdateRowHeight)
{
if (m_listsDirty || forceUpdateRowHeight) {
// don't get in here recursively
m_listsDirty = false;
int rowCount = 0;
// determine the number of rows we need to display all items
// while going through the list, check for erroneous transactions
for (QVector<RegisterItem*>::size_type i = 0; i < m_items.size(); ++i) {
RegisterItem* item = m_items[i];
if (!item)
continue;
item->setStartRow(rowCount);
item->setNeedResize();
rowCount += item->numRowsRegister();
if (item->isErroneous()) {
if (!m_firstErroneous)
m_firstErroneous = item;
m_lastErroneous = item;
}
}
updateAlternate();
// create item index
setupItemIndex(rowCount);
bool needUpdateHeaders = (QTableWidget::rowCount() != rowCount) | forceUpdateRowHeight;
// setup QTable. Make sure to suppress screen updates for now
setRowCount(rowCount);
// if we need to update the headers, we do it now for all rows
// again we make sure to suppress screen updates
if (needUpdateHeaders) {
for (int i = 0; i < rowCount; ++i) {
RegisterItem* item = itemAtRow(i);
if (item->isVisible()) {
showRow(i);
} else {
hideRow(i);
}
verticalHeader()->resizeSection(i, item->rowHeightHint());
}
verticalHeader()->setUpdatesEnabled(true);
}
// force resizeing of the columns if necessary
if (m_needInitialColumnResize) {
QTimer::singleShot(0, this, SLOT(resize()));
m_needInitialColumnResize = false;
} else {
update();
// if the number of rows changed, we might need to resize the register
// to make sure we reflect the current visibility of the scrollbars.
if (needUpdateHeaders)
QTimer::singleShot(0, this, SLOT(resize()));
}
}
}
int Register::rowHeightHint() const
{
if (!m_rowHeightHint) {
qDebug("Register::rowHeightHint(): m_rowHeightHint is zero!!");
}
return m_rowHeightHint;
}
void Register::focusInEvent(QFocusEvent* ev)
{
QTableWidget::focusInEvent(ev);
if (m_focusItem) {
m_focusItem->setFocus(true, false);
}
}
bool Register::event(QEvent* event)
{
if (event->type() == QEvent::ToolTip) {
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
// get the row, if it's the header, then we're done
// otherwise, adjust the row to be 0 based.
int row = rowAt(helpEvent->y());
if (!row)
return true;
--row;
int col = columnAt(helpEvent->x());
RegisterItem* item = itemAtRow(row);
if (!item)
return true;
row = row - item->startRow();
QString msg;
QRect rect;
if (!item->maybeTip(helpEvent->pos(), row, col, rect, msg))
return true;
if (!msg.isEmpty()) {
QToolTip::showText(helpEvent->globalPos(), msg);
} else {
QToolTip::hideText();
event->ignore();
}
return true;
}
return TransactionEditorContainer::event(event);
}
void Register::focusOutEvent(QFocusEvent* ev)
{
if (m_focusItem) {
m_focusItem->setFocus(false, false);
}
QTableWidget::focusOutEvent(ev);
}
void Register::resizeEvent(QResizeEvent* ev)
{
TransactionEditorContainer::resizeEvent(ev);
resize(DetailColumn, true);
}
void Register::resize()
{
resize(DetailColumn);
}
void Register::resize(int col, bool force)
{
if (!m_needResize && !force)
return;
m_needResize = false;
// resize the register
int w = viewport()->width();
// TODO I was playing a bit with manual ledger resizing but could not get
// a good solution. I just leave the code around, so that maybe others
// pick it up again. So far, it's not clear to me where to store the
// size of the sections:
//
// a) with the account (as it is done now)
// b) with the application for the specific account type
// c) ????
//
// Ideas are welcome (ipwizard: 2007-07-19)
// Note: currently there's no way to switch back to automatic
// column sizing once the manual sizing option has been saved
#if 0
if (m_account.value("kmm-ledger-column-width").isEmpty()) {
#endif
// check which space we need
if (columnWidth(NumberColumn))
adjustColumn(NumberColumn);
if (columnWidth(AccountColumn))
adjustColumn(AccountColumn);
if (columnWidth(PaymentColumn))
adjustColumn(PaymentColumn);
if (columnWidth(DepositColumn))
adjustColumn(DepositColumn);
if (columnWidth(QuantityColumn))
adjustColumn(QuantityColumn);
if (columnWidth(BalanceColumn))
adjustColumn(BalanceColumn);
if (columnWidth(PriceColumn))
adjustColumn(PriceColumn);
if (columnWidth(ValueColumn))
adjustColumn(ValueColumn);
// make amount columns all the same size
// only extend the entry columns to make sure they fit
// the widget
int dwidth = 0;
int ewidth = 0;
if (ewidth < columnWidth(PaymentColumn))
ewidth = columnWidth(PaymentColumn);
if (ewidth < columnWidth(DepositColumn))
ewidth = columnWidth(DepositColumn);
if (ewidth < columnWidth(QuantityColumn))
ewidth = columnWidth(QuantityColumn);
if (dwidth < columnWidth(BalanceColumn))
dwidth = columnWidth(BalanceColumn);
if (ewidth < columnWidth(PriceColumn))
ewidth = columnWidth(PriceColumn);
if (dwidth < columnWidth(ValueColumn))
dwidth = columnWidth(ValueColumn);
int swidth = columnWidth(SecurityColumn);
if (swidth > 0) {
adjustColumn(SecurityColumn);
swidth = columnWidth(SecurityColumn);
}
adjustColumn(DateColumn);
#ifndef KMM_DESIGNER
// Resize the date and money fields to either
// a) the size required by the input widget if no transaction form is shown and the register is used with an editor
// b) the adjusted value for the input widget if the transaction form is visible or an editor is not used
if (m_usedWithEditor && !KMyMoneyGlobalSettings::transactionForm()) {
QPushButton *pushButton = new QPushButton;
const int pushButtonSpacing = pushButton->sizeHint().width() + 5;
setColumnWidth(DateColumn, columnWidth(DateColumn) + pushButtonSpacing + 4/* space for the spinbox arrows */);
ewidth += pushButtonSpacing;
if (swidth > 0) {
// extend the security width to make space for the selector arrow
swidth = columnWidth(SecurityColumn) + 40;
}
delete pushButton;
}
#endif
if (columnWidth(PaymentColumn))
setColumnWidth(PaymentColumn, ewidth);
if (columnWidth(DepositColumn))
setColumnWidth(DepositColumn, ewidth);
if (columnWidth(QuantityColumn))
setColumnWidth(QuantityColumn, ewidth);
if (columnWidth(BalanceColumn))
setColumnWidth(BalanceColumn, dwidth);
if (columnWidth(PriceColumn))
setColumnWidth(PriceColumn, ewidth);
if (columnWidth(ValueColumn))
setColumnWidth(ValueColumn, dwidth);
if (columnWidth(ReconcileFlagColumn))
setColumnWidth(ReconcileFlagColumn, 20);
if (swidth > 0)
setColumnWidth(SecurityColumn, swidth);
#if 0
// see comment above
} else {
QStringList colSizes = QStringList::split(",", m_account.value("kmm-ledger-column-width"), true);
for (int i; i < colSizes.count(); ++i) {
int colWidth = colSizes[i].toInt();
if (colWidth == 0)
continue;
setColumnWidth(i, w * colWidth / 100);
}
}
#endif
for (int i = 0; i < columnCount(); ++i) {
if (i == col)
continue;
w -= columnWidth(i);
}
setColumnWidth(col, w);
}
int Register::minimumColumnWidth(int col)
{
QHeaderView *topHeader = horizontalHeader();
int w = topHeader->fontMetrics().width(horizontalHeaderItem(col) ? horizontalHeaderItem(col)->text() : QString()) + 10;
w = qMax(w, 20);
#ifdef KMM_DESIGNER
return w;
#else
int maxWidth = 0;
int minWidth = 0;
QFontMetrics cellFontMetrics(KMyMoneyGlobalSettings::listCellFont());
switch (col) {
case DateColumn:
minWidth = cellFontMetrics.width(QLocale().toString(QDate(6999, 12, 29), QLocale::ShortFormat) + " ");
break;
default:
break;
}
// scan through the transactions
for (int i = 0; i < m_items.size(); ++i) {
RegisterItem* const item = m_items[i];
if (!item)
continue;
Transaction* t = dynamic_cast<Transaction*>(item);
if (t) {
int nw = 0;
try {
nw = t->registerColWidth(col, cellFontMetrics);
} catch (const MyMoneyException &) {
// This should only be reached if the data in the file disappeared
// from under us, such as when the account was deleted from a
// different view, then this view is restored. In this case, new
// data is about to be loaded into the view anyway, so just remove
// the item from the register and swallow the exception.
//qDebug("%s", qPrintable(e.what()));
removeItem(t);
}
w = qMax(w, nw);
if (maxWidth) {
if (w > maxWidth) {
w = maxWidth;
break;
}
}
if (w < minWidth) {
w = minWidth;
break;
}
}
}
return w;
#endif
}
void Register::adjustColumn(int col)
{
setColumnWidth(col, minimumColumnWidth(col));
}
void Register::clearSelection()
{
unselectItems();
TransactionEditorContainer::clearSelection();
}
void Register::doSelectItems(int from, int to, bool selected)
{
int start, end;
// make sure start is smaller than end
if (from <= to) {
start = from;
end = to;
} else {
start = to;
end = from;
}
// make sure we stay in bounds
if (start < 0)
start = 0;
if ((end <= -1) || (end > (m_items.size() - 1)))
end = m_items.size() - 1;
RegisterItem* firstItem;
RegisterItem* lastItem;
firstItem = lastItem = 0;
for (int i = start; i <= end; ++i) {
RegisterItem* const item = m_items[i];
if (item) {
if (selected != item->isSelected()) {
if (!firstItem)
firstItem = item;
item->setSelected(selected);
lastItem = item;
}
}
}
}
RegisterItem* Register::itemAtRow(int row) const
{
if (row >= 0 && row < m_itemIndex.size()) {
return m_itemIndex[row];
}
return 0;
}
int Register::rowToIndex(int row) const
{
for (int i = 0; i < m_items.size(); ++i) {
RegisterItem* const item = m_items[i];
if (!item)
continue;
if (row >= item->startRow() && row < (item->startRow() + item->numRowsRegister()))
return i;
}
return -1;
}
void Register::selectedTransactions(SelectedTransactions& list) const
{
if (m_focusItem && m_focusItem->isSelected() && m_focusItem->isVisible()) {
Transaction* t = dynamic_cast<Transaction*>(m_focusItem);
if (t) {
QString id;
if (t->isScheduled())
id = t->transaction().id();
SelectedTransaction s(t->transaction(), t->split(), id);
list << s;
}
}
for (int i = 0; i < m_items.size(); ++i) {
RegisterItem* const item = m_items[i];
// make sure, we don't include the focus item twice
if (item == m_focusItem)
continue;
if (item && item->isSelected() && item->isVisible()) {
Transaction* t = dynamic_cast<Transaction*>(item);
if (t) {
QString id;
if (t->isScheduled())
id = t->transaction().id();
SelectedTransaction s(t->transaction(), t->split(), id);
list << s;
}
}
}
}
QList<RegisterItem*> Register::selectedItems() const
{
QList<RegisterItem*> list;
RegisterItem* item = m_firstItem;
while (item) {
if (item && item->isSelected() && item->isVisible()) {
list << item;
}
item = item->nextItem();
}
return list;
}
int Register::selectedItemsCount() const
{
int cnt = 0;
RegisterItem* item = m_firstItem;
while (item) {
if (item->isSelected() && item->isVisible())
++cnt;
item = item->nextItem();
}
return cnt;
}
void Register::mouseReleaseEvent(QMouseEvent *e)
{
if (e->button() == Qt::RightButton) {
// see the comment in Register::contextMenuEvent
// on Linux we never get here but on Windows this
// event is fired before the contextMenuEvent which
// causes the loss of the multiple selection; to avoid
// this just ignore the event and act like on Linux
return;
}
if (m_ignoreNextButtonRelease) {
m_ignoreNextButtonRelease = false;
return;
}
m_mouseButton = e->button();
m_modifiers = QApplication::keyboardModifiers();
QTableWidget::mouseReleaseEvent(e);
}
void Register::contextMenuEvent(QContextMenuEvent *e)
{
if (e->reason() == QContextMenuEvent::Mouse) {
// since mouse release event is not called, we need
// to reset the mouse button and the modifiers here
m_mouseButton = Qt::NoButton;
m_modifiers = Qt::NoModifier;
// if a selected item is clicked don't change the selection
RegisterItem* item = itemAtRow(rowAt(e->y()));
if (item && !item->isSelected())
selectItem(rowAt(e->y()), columnAt(e->x()));
}
openContextMenu();
}
void Register::selectItem(int row, int col)
{
if (row >= 0 && row < m_itemIndex.size()) {
RegisterItem* item = m_itemIndex[row];
// don't support selecting when the item has an editor
// or the item itself is not selectable
if (item->hasEditorOpen() || !item->isSelectable()) {
m_mouseButton = Qt::NoButton;
return;
}
QString id = item->id();
selectItem(item);
// selectItem() might have changed the pointers, so we
// need to reconstruct it here
item = itemById(id);
Transaction* t = dynamic_cast<Transaction*>(item);
if (t) {
if (!id.isEmpty()) {
if (t && col == ReconcileFlagColumn && selectedItemsCount() == 1 && !t->isScheduled())
emit reconcileStateColumnClicked(t);
} else {
emit emptyItemSelected();
}
}
}
}
void Register::setAnchorItem(RegisterItem* anchorItem)
{
m_selectAnchor = anchorItem;
}
bool Register::setFocusItem(RegisterItem* focusItem)
{
if (focusItem && focusItem->canHaveFocus()) {
if (m_focusItem) {
m_focusItem->setFocus(false);
}
Transaction* item = dynamic_cast<Transaction*>(focusItem);
if (m_focusItem != focusItem && item) {
emit focusChanged(item);
}
m_focusItem = focusItem;
m_focusItem->setFocus(true);
if (m_listsDirty)
updateRegister(KMyMoneyGlobalSettings::ledgerLens() | !KMyMoneyGlobalSettings::transactionForm());
ensureItemVisible(m_focusItem);
return true;
} else
return false;
}
bool Register::setFocusToTop()
{
RegisterItem* rgItem = m_firstItem;
while (rgItem) {
if (setFocusItem(rgItem))
return true;
rgItem = rgItem->nextItem();
}
return false;
}
void Register::selectItem(RegisterItem* item, bool dontChangeSelections)
{
if (!item)
return;
Qt::MouseButtons buttonState = m_mouseButton;
Qt::KeyboardModifiers modifiers = m_modifiers;
m_mouseButton = Qt::NoButton;
m_modifiers = Qt::NoModifier;
if (m_selectionMode == NoSelection)
return;
if (item->isSelectable()) {
QString id = item->id();
QList<RegisterItem*> itemList = selectedItems();
bool okToSelect = true;
int cnt = itemList.count();
const bool scheduledTransactionSelected = (cnt > 0 && itemList.front() && (typeid(*(itemList.front())) == typeid(StdTransactionScheduled)));
if (buttonState & Qt::LeftButton) {
if (!(modifiers & (Qt::ShiftModifier | Qt::ControlModifier))
|| (m_selectAnchor == 0)) {
if ((cnt != 1) || ((cnt == 1) && !item->isSelected())) {
emit aboutToSelectItem(item, okToSelect);
if (okToSelect) {
// pointer 'item' might have changed. reconstruct it.
item = itemById(id);
unselectItems();
item->setSelected(true);
setFocusItem(item);
}
}
if (okToSelect)
m_selectAnchor = item;
}
if (m_selectionMode == MultiSelection) {
switch (modifiers & (Qt::ShiftModifier | Qt::ControlModifier)) {
case Qt::ControlModifier:
if (scheduledTransactionSelected || typeid(*item) == typeid(StdTransactionScheduled))
okToSelect = false;
// toggle selection state of current item
emit aboutToSelectItem(item, okToSelect);
if (okToSelect) {
// pointer 'item' might have changed. reconstruct it.
item = itemById(id);
item->setSelected(!item->isSelected());
setFocusItem(item);
}
break;
case Qt::ShiftModifier:
if (scheduledTransactionSelected || typeid(*item) == typeid(StdTransactionScheduled))
okToSelect = false;
emit aboutToSelectItem(item, okToSelect);
if (okToSelect) {
// pointer 'item' might have changed. reconstruct it.
item = itemById(id);
unselectItems();
selectItems(rowToIndex(m_selectAnchor->startRow()), rowToIndex(item->startRow()));
setFocusItem(item);
}
break;
}
}
} else {
// we get here when called by application logic
emit aboutToSelectItem(item, okToSelect);
if (okToSelect) {
// pointer 'item' might have changed. reconstruct it.
item = itemById(id);
if (!dontChangeSelections)
unselectItems();
item->setSelected(true);
setFocusItem(item);
m_selectAnchor = item;
}
}
if (okToSelect) {
SelectedTransactions list(this);
emit transactionsSelected(list);
}
}
}
void Register::ensureItemVisible(RegisterItem* item)
{
if (!item)
return;
m_ensureVisibleItem = item;
QTimer::singleShot(0, this, SLOT(slotEnsureItemVisible()));
}
void Register::slotDoubleClicked(int row, int)
{
if (row >= 0 && row < m_itemIndex.size()) {
RegisterItem* p = m_itemIndex[row];
if (p->isSelectable()) {
m_ignoreNextButtonRelease = true;
// double click to start editing only works if the focus
// item is among the selected ones
if (!focusItem()) {
setFocusItem(p);
if (m_selectionMode != NoSelection)
p->setSelected(true);
}
if (m_focusItem->isSelected()) {
// don't emit the signal right away but wait until
// we come back to the Qt main loop
QTimer::singleShot(0, this, SIGNAL(editTransaction()));
}
}
}
}
void Register::slotEnsureItemVisible()
{
// if clear() has been called since the timer was
// started, we just ignore the call
if (!m_ensureVisibleItem)
return;
// make sure to catch latest changes
setUpdatesEnabled(false);
updateRegister();
setUpdatesEnabled(true);
// since the item will be made visible at the top of the viewport make the bottom index visible first to make the whole item visible
scrollTo(model()->index(m_ensureVisibleItem->startRow() + m_ensureVisibleItem->numRowsRegister() - 1, DetailColumn));
scrollTo(model()->index(m_ensureVisibleItem->startRow(), DetailColumn));
}
TransactionSortField KMyMoneyRegister::textToSortOrder(const QString& text)
{
for (int idx = 1; idx < static_cast<int>(MaxSortFields); ++idx) {
if (text == i18n(sortOrderText[idx])) {
return static_cast<TransactionSortField>(idx);
}
}
return UnknownSort;
}
const QString KMyMoneyRegister::sortOrderToText(TransactionSortField idx)
{
if (idx < PostDateSort || idx >= MaxSortFields)
idx = UnknownSort;
return i18n(sortOrderText[idx]);
}
QString Register::text(int /*row*/, int /*col*/) const
{
return QString("a");
}
QWidget* Register::createEditor(int /*row*/, int /*col*/, bool /*initFromCell*/) const
{
return 0;
}
void Register::setCellContentFromEditor(int /*row*/, int /*col*/)
{
}
void Register::endEdit(int /*row*/, int /*col*/, bool /*accept*/, bool /*replace*/)
{
}
void Register::arrangeEditWidgets(QMap<QString, QWidget*>& editWidgets, KMyMoneyRegister::Transaction* t)
{
t->arrangeWidgetsInRegister(editWidgets);
ensureItemVisible(t);
// updateContents();
}
void Register::tabOrder(QWidgetList& tabOrderWidgets, KMyMoneyRegister::Transaction* t) const
{
t->tabOrderInRegister(tabOrderWidgets);
}
void Register::removeEditWidgets(QMap<QString, QWidget*>& editWidgets)
{
// remove pointers from map
QMap<QString, QWidget*>::iterator it;
for (it = editWidgets.begin(); it != editWidgets.end();) {
if ((*it)->parentWidget() == this) {
editWidgets.erase(it);
it = editWidgets.begin();
} else
++it;
}
// now delete the widgets
KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(focusItem());
for (int row = t->startRow(); row < t->startRow() + t->numRowsRegister(true); ++row) {
for (int col = 0; col < columnCount(); ++col) {
if (cellWidget(row, col)) {
cellWidget(row, col)->hide();
setCellWidget(row, col, 0);
}
}
// make sure to reduce the possibly size to what it was before editing started
setRowHeight(row, t->rowHeightHint());
}
}
RegisterItem* Register::itemById(const QString& id) const
{
if (id.isEmpty())
return m_lastItem;
for (QVector<RegisterItem*>::size_type i = 0; i < m_items.size(); ++i) {
RegisterItem* item = m_items[i];
if (!item)
continue;
if (item->id() == id)
return item;
}
return 0;
}
void Register::handleItemChange(RegisterItem* old, bool shift, bool control)
{
if (m_selectionMode == MultiSelection) {
if (shift) {
selectRange(m_selectAnchor ? m_selectAnchor : old,
m_focusItem, false, true, (m_selectAnchor && !control) ? true : false);
} else if (!control) {
selectItem(m_focusItem, false);
}
}
}
void Register::selectRange(RegisterItem* from, RegisterItem* to, bool invert, bool includeFirst, bool clearSel)
{
if (!from || !to)
return;
if (from == to && !includeFirst)
return;
bool swap = false;
if (to == from->prevItem())
swap = true;
RegisterItem* item;
if (!swap && from != to && from != to->prevItem()) {
bool found = false;
for (item = from; item; item = item->nextItem()) {
if (item == to) {
found = true;
break;
}
}
if (!found)
swap = true;
}
if (swap) {
item = from;
from = to;
to = item;
if (!includeFirst)
to = to->prevItem();
} else if (!includeFirst) {
from = from->nextItem();
}
if (clearSel) {
for (item = firstItem(); item; item = item->nextItem()) {
if (item->isSelected() && item->isVisible()) {
item->setSelected(false);
}
}
}
for (item = from; item; item = item->nextItem()) {
if (item->isSelectable()) {
if (!invert) {
if (!item->isSelected() && item->isVisible()) {
item->setSelected(true);
}
} else {
bool sel = !item->isSelected();
if ((item->isSelected() != sel) && item->isVisible()) {
item->setSelected(sel);
}
}
}
if (item == to)
break;
}
}
void Register::scrollPage(int key, Qt::KeyboardModifiers modifiers)
{
RegisterItem* oldFocusItem = m_focusItem;
// make sure we have a focus item
if (!m_focusItem)
setFocusItem(m_firstItem);
if (!m_focusItem && m_firstItem)
setFocusItem(m_firstItem->nextItem());
if (!m_focusItem)
return;
RegisterItem* item = m_focusItem;
int height = 0;
switch (key) {
case Qt::Key_PageUp:
while (height < viewport()->height() && item->prevItem()) {
do {
item = item->prevItem();
if (item->isVisible())
height += item->rowHeightHint();
} while ((!item->isSelectable() || !item->isVisible()) && item->prevItem());
while ((!item->isSelectable() || !item->isVisible()) && item->nextItem())
item = item->nextItem();
}
break;
case Qt::Key_PageDown:
while (height < viewport()->height() && item->nextItem()) {
do {
if (item->isVisible())
height += item->rowHeightHint();
item = item->nextItem();
} while ((!item->isSelectable() || !item->isVisible()) && item->nextItem());
while ((!item->isSelectable() || !item->isVisible()) && item->prevItem())
item = item->prevItem();
}
break;
case Qt::Key_Up:
if (item->prevItem()) {
do {
item = item->prevItem();
} while ((!item->isSelectable() || !item->isVisible()) && item->prevItem());
}
break;
case Qt::Key_Down:
if (item->nextItem()) {
do {
item = item->nextItem();
} while ((!item->isSelectable() || !item->isVisible()) && item->nextItem());
}
break;
case Qt::Key_Home:
item = m_firstItem;
while ((!item->isSelectable() || !item->isVisible()) && item->nextItem())
item = item->nextItem();
break;
case Qt::Key_End:
item = m_lastItem;
while ((!item->isSelectable() || !item->isVisible()) && item->prevItem())
item = item->prevItem();
break;
}
// make sure to avoid selecting a possible empty transaction at the end
Transaction* t = dynamic_cast<Transaction*>(item);
if (t && t->transaction().id().isEmpty()) {
if (t->prevItem()) {
item = t->prevItem();
}
}
if (!(modifiers & Qt::ShiftModifier) || !m_selectAnchor)
m_selectAnchor = item;
setFocusItem(item);
if (item->isSelectable()) {
handleItemChange(oldFocusItem, modifiers & Qt::ShiftModifier, modifiers & Qt::ControlModifier);
// tell the world about the changes in selection
SelectedTransactions list(this);
emit transactionsSelected(list);
}
if (m_focusItem && !m_focusItem->isSelected() && m_selectionMode == SingleSelection)
selectItem(item);
}
void Register::keyPressEvent(QKeyEvent* ev)
{
switch (ev->key()) {
case Qt::Key_Space:
if (m_selectionMode != NoSelection) {
// get the state out of the event ...
m_modifiers = ev->modifiers();
// ... and pretend that we have pressed the left mouse button ;)
m_mouseButton = Qt::LeftButton;
selectItem(m_focusItem);
}
break;
case Qt::Key_PageUp:
case Qt::Key_PageDown:
case Qt::Key_Home:
case Qt::Key_End:
case Qt::Key_Down:
case Qt::Key_Up:
scrollPage(ev->key(), ev->modifiers());
break;
case Qt::Key_Enter:
case Qt::Key_Return:
// don't emit the signal right away but wait until
// we come back to the Qt main loop
QTimer::singleShot(0, this, SIGNAL(editTransaction()));
break;
default:
QTableWidget::keyPressEvent(ev);
break;
}
}
Transaction* Register::transactionFactory(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId)
{
Transaction* t = 0;
MyMoneySplit s = split;
if (parent->account() == MyMoneyAccount()) {
t = new KMyMoneyRegister::StdTransaction(parent, transaction, s, uniqueId);
return t;
}
switch (parent->account().accountType()) {
- case MyMoneyAccount::Checkings:
- case MyMoneyAccount::Savings:
- case MyMoneyAccount::Cash:
- case MyMoneyAccount::CreditCard:
- case MyMoneyAccount::Loan:
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Currency:
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
- case MyMoneyAccount::AssetLoan:
- case MyMoneyAccount::Equity:
+ case Account::Checkings:
+ case Account::Savings:
+ case Account::Cash:
+ case Account::CreditCard:
+ case Account::Loan:
+ case Account::Asset:
+ case Account::Liability:
+ case Account::Currency:
+ case Account::Income:
+ case Account::Expense:
+ case Account::AssetLoan:
+ case Account::Equity:
if (s.accountId().isEmpty())
s.setAccountId(parent->account().id());
if (s.isMatched())
t = new KMyMoneyRegister::StdTransactionMatched(parent, transaction, s, uniqueId);
else if (transaction.isImported())
t = new KMyMoneyRegister::StdTransactionDownloaded(parent, transaction, s, uniqueId);
else
t = new KMyMoneyRegister::StdTransaction(parent, transaction, s, uniqueId);
break;
- case MyMoneyAccount::Investment:
+ case Account::Investment:
if (s.isMatched())
t = new KMyMoneyRegister::InvestTransaction/* Matched */(parent, transaction, s, uniqueId);
else if (transaction.isImported())
t = new KMyMoneyRegister::InvestTransactionDownloaded(parent, transaction, s, uniqueId);
else
t = new KMyMoneyRegister::InvestTransaction(parent, transaction, s, uniqueId);
break;
- case MyMoneyAccount::CertificateDep:
- case MyMoneyAccount::MoneyMarket:
- case MyMoneyAccount::Stock:
+ case Account::CertificateDep:
+ case Account::MoneyMarket:
+ case Account::Stock:
default:
- qDebug("Register::transactionFactory: invalid accountTypeE %d", parent->account().accountType());
+ qDebug("Register::transactionFactory: invalid accountTypeE %d", (int)parent->account().accountType());
break;
}
return t;
}
void Register::addGroupMarkers()
{
QMap<QString, int> list;
QMap<QString, int>::const_iterator it;
KMyMoneyRegister::RegisterItem* p = firstItem();
KMyMoneyRegister::Transaction* t;
QString name;
QDate today;
QDate yesterday, thisWeek, lastWeek;
QDate thisMonth, lastMonth;
QDate thisYear;
int weekStartOfs;
switch (primarySortKey()) {
case KMyMoneyRegister::PostDateSort:
case KMyMoneyRegister::EntryDateSort:
today = QDate::currentDate();
thisMonth.setDate(today.year(), today.month(), 1);
lastMonth = thisMonth.addMonths(-1);
yesterday = today.addDays(-1);
// a = QDate::dayOfWeek() todays weekday (1 = Monday, 7 = Sunday)
// b = QLocale().firstDayOfWeek() first day of week (1 = Monday, 7 = Sunday)
weekStartOfs = today.dayOfWeek() - QLocale().firstDayOfWeek();
if (weekStartOfs < 0) {
weekStartOfs = 7 + weekStartOfs;
}
thisWeek = today.addDays(-weekStartOfs);
lastWeek = thisWeek.addDays(-7);
thisYear.setDate(today.year(), 1, 1);
if (KMyMoneyGlobalSettings::startDate().date() != QDate(1900, 1, 1))
new KMyMoneyRegister::FancyDateGroupMarker(this, KMyMoneyGlobalSettings::startDate().date(), i18n("Prior transactions possibly filtered"));
if (KMyMoneyGlobalSettings::showFancyMarker()) {
if (m_account.lastReconciliationDate().isValid())
new KMyMoneyRegister::StatementGroupMarker(this, KMyMoneyRegister::Deposit, m_account.lastReconciliationDate(), i18n("Last reconciliation"));
if (!m_account.value("lastImportedTransactionDate").isEmpty()
&& !m_account.value("lastStatementBalance").isEmpty()) {
MyMoneyMoney balance(m_account.value("lastStatementBalance"));
- if (m_account.accountGroup() == MyMoneyAccount::Liability)
+ if (m_account.accountGroup() == Account::Liability)
balance = -balance;
QString txt = i18n("Online Statement Balance: %1", balance.formatMoney(m_account.fraction()));
KMyMoneyRegister::StatementGroupMarker *p = new KMyMoneyRegister::StatementGroupMarker(this, KMyMoneyRegister::Deposit, QDate::fromString(m_account.value("lastImportedTransactionDate"), Qt::ISODate), txt);
p->setErroneous(!MyMoneyFile::instance()->hasMatchingOnlineBalance(m_account));
}
new KMyMoneyRegister::FancyDateGroupMarker(this, thisYear, i18n("This year"));
new KMyMoneyRegister::FancyDateGroupMarker(this, lastMonth, i18n("Last month"));
new KMyMoneyRegister::FancyDateGroupMarker(this, thisMonth, i18n("This month"));
new KMyMoneyRegister::FancyDateGroupMarker(this, lastWeek, i18n("Last week"));
new KMyMoneyRegister::FancyDateGroupMarker(this, thisWeek, i18n("This week"));
new KMyMoneyRegister::FancyDateGroupMarker(this, yesterday, i18n("Yesterday"));
new KMyMoneyRegister::FancyDateGroupMarker(this, today, i18n("Today"));
new KMyMoneyRegister::FancyDateGroupMarker(this, today.addDays(1), i18n("Future transactions"));
new KMyMoneyRegister::FancyDateGroupMarker(this, thisWeek.addDays(7), i18n("Next week"));
new KMyMoneyRegister::FancyDateGroupMarker(this, thisMonth.addMonths(1), i18n("Next month"));
} else {
new KMyMoneyRegister::SimpleDateGroupMarker(this, today.addDays(1), i18n("Future transactions"));
}
if (KMyMoneyGlobalSettings::showFiscalMarker()) {
QDate currentFiscalYear = KMyMoneyGlobalSettings::firstFiscalDate();
new KMyMoneyRegister::FiscalYearGroupMarker(this, currentFiscalYear, i18n("Current fiscal year"));
new KMyMoneyRegister::FiscalYearGroupMarker(this, currentFiscalYear.addYears(-1), i18n("Previous fiscal year"));
new KMyMoneyRegister::FiscalYearGroupMarker(this, currentFiscalYear.addYears(1), i18n("Next fiscal year"));
}
break;
case KMyMoneyRegister::TypeSort:
if (KMyMoneyGlobalSettings::showFancyMarker()) {
new KMyMoneyRegister::TypeGroupMarker(this, KMyMoneyRegister::Deposit, m_account.accountType());
new KMyMoneyRegister::TypeGroupMarker(this, KMyMoneyRegister::Payment, m_account.accountType());
}
break;
case KMyMoneyRegister::ReconcileStateSort:
if (KMyMoneyGlobalSettings::showFancyMarker()) {
new KMyMoneyRegister::ReconcileGroupMarker(this, MyMoneySplit::NotReconciled);
new KMyMoneyRegister::ReconcileGroupMarker(this, MyMoneySplit::Cleared);
new KMyMoneyRegister::ReconcileGroupMarker(this, MyMoneySplit::Reconciled);
new KMyMoneyRegister::ReconcileGroupMarker(this, MyMoneySplit::Frozen);
}
break;
case KMyMoneyRegister::PayeeSort:
if (KMyMoneyGlobalSettings::showFancyMarker()) {
while (p) {
t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
if (t) {
list[t->sortPayee()] = 1;
}
p = p->nextItem();
}
for (it = list.constBegin(); it != list.constEnd(); ++it) {
name = it.key();
if (name.isEmpty()) {
name = i18nc("Unknown payee", "Unknown");
}
new KMyMoneyRegister::PayeeGroupMarker(this, name);
}
}
break;
case KMyMoneyRegister::CategorySort:
if (KMyMoneyGlobalSettings::showFancyMarker()) {
while (p) {
t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
if (t) {
list[t->sortCategory()] = 1;
}
p = p->nextItem();
}
for (it = list.constBegin(); it != list.constEnd(); ++it) {
name = it.key();
if (name.isEmpty()) {
name = i18nc("Unknown category", "Unknown");
}
new KMyMoneyRegister::CategoryGroupMarker(this, name);
}
}
break;
case KMyMoneyRegister::SecuritySort:
if (KMyMoneyGlobalSettings::showFancyMarker()) {
while (p) {
t = dynamic_cast<KMyMoneyRegister::InvestTransaction*>(p);
if (t) {
list[t->sortSecurity()] = 1;
}
p = p->nextItem();
}
for (it = list.constBegin(); it != list.constEnd(); ++it) {
name = it.key();
if (name.isEmpty()) {
name = i18nc("Unknown security", "Unknown");
}
new KMyMoneyRegister::CategoryGroupMarker(this, name);
}
}
break;
default: // no markers supported
break;
}
}
void Register::removeUnwantedGroupMarkers()
{
// remove all trailing group markers except statement markers
KMyMoneyRegister::RegisterItem* q;
KMyMoneyRegister::RegisterItem* p = lastItem();
while (p) {
q = p;
if (dynamic_cast<KMyMoneyRegister::Transaction*>(p)
|| dynamic_cast<KMyMoneyRegister::StatementGroupMarker*>(p))
break;
p = p->prevItem();
delete q;
}
// remove all adjacent group markers
bool lastWasGroupMarker = false;
p = lastItem();
while (p) {
q = p;
KMyMoneyRegister::GroupMarker* m = dynamic_cast<KMyMoneyRegister::GroupMarker*>(p);
p = p->prevItem();
if (m) {
m->markVisible(true);
// make adjacent group marker invisible except those that show statement information
if (lastWasGroupMarker && (dynamic_cast<KMyMoneyRegister::StatementGroupMarker*>(m) == 0)) {
m->markVisible(false);
}
lastWasGroupMarker = true;
} else if (q->isVisible())
lastWasGroupMarker = false;
}
}
DetailsColumnType Register::getDetailsColumnType() const
{
return m_detailsColumnType;
}
void Register::setDetailsColumnType(DetailsColumnType detailsColumnType)
{
m_detailsColumnType = detailsColumnType;
}
diff --git a/kmymoney/widgets/register.h b/kmymoney/widgets/register.h
index d1a42ac78..af69a8400 100644
--- a/kmymoney/widgets/register.h
+++ b/kmymoney/widgets/register.h
@@ -1,659 +1,659 @@
/***************************************************************************
register.h
----------
begin : Fri Mar 10 2006
copyright : (C) 2006 by Thomas Baumgart
email : Thomas Baumgart <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 REGISTER_H
#define REGISTER_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QVector>
#include <QWidget>
#include <QMap>
#include <QList>
#include <QStyledItemDelegate>
#include <QStyleOptionViewItem>
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyaccount.h"
#include "registeritem.h"
#include "transaction.h"
#include "transactioneditorcontainer.h"
#include "selectedtransaction.h"
class MyMoneyTransaction;
namespace KMyMoneyRegister
{
typedef enum {
UnknownSort = 0, ///< unknown sort criteria
PostDateSort = 1, ///< sort by post date
EntryDateSort, ///< sort by entry date
PayeeSort, ///< sort by payee name
ValueSort, ///< sort by value
NoSort, ///< sort by number field
EntryOrderSort, ///< sort by entry order
TypeSort, ///< sort by CashFlowDirection
CategorySort, ///< sort by Category
ReconcileStateSort, ///< sort by reconciliation state
SecuritySort, ///< sort by security (only useful for investment accounts)
// insert new values in front of this line
MaxSortFields
} TransactionSortField;
typedef enum {
AscendingOrder = 0, ///< sort in ascending order
DescendingOrder ///< sort in descending order
} SortDirection;
typedef enum {
PayeeFirst = 0, ///< show the payee on the first row of the transaction in the details column and the account on the second
AccountFirst ///< show the account on the first row of the transaction in the details column and the payee on the second
} DetailsColumnType;
class Register;
class RegisterItem;
class ItemPtrVector;
const QString sortOrderToText(TransactionSortField idx);
TransactionSortField textToSortOrder(const QString& text);
class QWidgetContainer : public QMap<QString, QWidget*>
{
public:
QWidgetContainer() {}
QWidget* haveWidget(const QString& name) const {
QWidgetContainer::const_iterator it_w;
it_w = find(name);
if (it_w != end())
return *it_w;
return 0;
}
void removeOrphans() {
QWidgetContainer::iterator it_w;
for (it_w = begin(); it_w != end();) {
if ((*it_w) && (*it_w)->parent())
++it_w;
else {
delete(*it_w);
remove(it_w.key());
it_w = begin();
}
}
}
};
class GroupMarker : public RegisterItem
{
public:
explicit GroupMarker(Register* parent, const QString& txt = QString());
~GroupMarker();
void setText(const QString& txt) {
m_txt = txt;
}
const QString& text() const {
return m_txt;
}
bool isSelectable() const {
return false;
}
bool canHaveFocus() const {
return false;
}
int numRows() const {
return 1;
}
virtual const char* className() {
return "GroupMarker";
}
bool isErroneous() const {
return m_erroneous;
}
void paintRegisterCell(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index);
void paintFormCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index);
int rowHeightHint() const;
bool matches(const RegisterFilter&) const {
return true;
}
virtual int sortSamePostDate() const {
return 0;
}
void setErroneous(bool condition = true) {
m_erroneous = condition;
}
protected:
QString m_txt;
bool m_showDate;
static QPixmap* m_bg;
static int m_bgRefCnt;
bool m_erroneous;
};
class FancyDateGroupMarker : public GroupMarker
{
public:
FancyDateGroupMarker(Register* parent, const QDate& date, const QString& txt);
virtual const QDate& sortPostDate() const {
return m_date;
}
virtual const QDate& sortEntryDate() const {
return m_date;
}
virtual const char* className() {
return "FancyDateGroupMarker";
}
private:
QDate m_date;
};
class StatementGroupMarker : public FancyDateGroupMarker
{
public:
StatementGroupMarker(Register* parent, CashFlowDirection dir, const QDate& date, const QString& txt);
CashFlowDirection sortType() const {
return m_dir;
}
virtual int sortSamePostDate() const {
return 3;
}
private:
CashFlowDirection m_dir;
};
class SimpleDateGroupMarker : public FancyDateGroupMarker
{
public:
SimpleDateGroupMarker(Register* parent, const QDate& date, const QString& txt);
void paintRegisterCell(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index);
int rowHeightHint() const;
virtual const char* className() {
return "SimpleDateGroupMarker";
}
};
class TypeGroupMarker : public GroupMarker
{
public:
- TypeGroupMarker(Register* parent, CashFlowDirection dir, MyMoneyAccount::accountTypeE accType);
+ TypeGroupMarker(Register* parent, CashFlowDirection dir, eMyMoney::Account accType);
CashFlowDirection sortType() const {
return m_dir;
}
private:
CashFlowDirection m_dir;
};
class FiscalYearGroupMarker : public FancyDateGroupMarker
{
public:
FiscalYearGroupMarker(Register* parent, const QDate& date, const QString& txt);
virtual const char* className() {
return "FiscalYearGroupMarker";
}
virtual int sortSamePostDate() const {
return 1;
}
};
class PayeeGroupMarker : public GroupMarker
{
public:
PayeeGroupMarker(Register* parent, const QString& name);
const QString& sortPayee() const {
return m_txt;
}
};
class CategoryGroupMarker : public GroupMarker
{
public:
CategoryGroupMarker(Register* parent, const QString& category);
const QString& sortCategory() const {
return m_txt;
}
const QString sortSecurity() const {
return m_txt;
}
virtual const char* className() {
return "CategoryGroupMarker";
}
};
class ReconcileGroupMarker : public GroupMarker
{
public:
ReconcileGroupMarker(Register* parent, MyMoneySplit::reconcileFlagE state);
virtual MyMoneySplit::reconcileFlagE sortReconcileState() const {
return m_state;
}
private:
MyMoneySplit::reconcileFlagE m_state;
};
class ItemPtrVector : public QVector<RegisterItem *>
{
public:
ItemPtrVector() {}
void sort();
protected:
/**
* sorter's compare routine. Returns true if i1 < i2
*/
static bool item_cmp(RegisterItem* i1, RegisterItem* i2);
};
class RegisterItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit RegisterItemDelegate(Register *parent);
~RegisterItemDelegate();
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
private:
Register *m_register;
};
class Register : public TransactionEditorContainer
{
Q_OBJECT
friend class Transaction;
friend class StdTransaction;
friend class InvestTransaction;
public:
Register(QWidget *parent = 0);
virtual ~Register();
/**
* add the item @a p to the register
*/
void addItem(RegisterItem* p);
/**
* insert the item @a p into the register after item @a q
*/
void insertItemAfter(RegisterItem* p, RegisterItem* q);
/**
* remove the item @p from the register
*/
void removeItem(RegisterItem* p);
/**
* This method returns a list of pointers to all selected items
* in the register
*
* @retval QList<RegisterItem*>
*/
QList<RegisterItem*> selectedItems() const;
/**
* Construct a list of all currently selected transactions in the register.
* If the current item carrying the focus (see focusItem() ) is selected
* it will be the first one contained in the list.
*
* @param list reference to QList receiving the SelectedTransaction()'s
*/
void selectedTransactions(SelectedTransactions& list) const;
QString text(int row, int col) const;
QWidget* createEditor(int row, int col, bool initFromCell) const;
void setCellContentFromEditor(int row, int col);
void endEdit(int row, int col, bool accept, bool replace);
RegisterItem* focusItem() const {
return m_focusItem;
}
RegisterItem* anchorItem() const {
return m_selectAnchor;
}
/**
* set focus to specific item.
* @return true if the item got focus
*/
bool setFocusItem(RegisterItem* focusItem);
void setAnchorItem(RegisterItem* anchorItem);
/**
* Set focus to the first focussable item
* @return true if a focussable item was found
*/
bool setFocusToTop();
/**
* Select @a item and unselect all others if @a dontChangeSelections
* is @a false. If m_buttonState differs from Qt::NoButton (method is
* called as a result of a mouse button press), then the setting of
* @a dontChangeSelections has no effect.
*/
void selectItem(RegisterItem* item, bool dontChangeSelections = false);
/**
* Clears all items in the register. All objects
* added to the register will be deleted.
*/
void clear();
void updateRegister(bool forceUpdateRowHeight = false);
/**
* Assign all visible items an alternate background color
*/
void updateAlternate() const;
/**
* make sure, we only show a single marker in a row
* through hiding unused ones
*/
void suppressAdjacentMarkers();
/**
* Adjusts column @a col so that all data fits in width.
*/
void adjustColumn(int col);
/**
* Convenience method to setup the register to show the columns
* based on the account type of @a account. If @a showAccountColumn
* is @a true then the account column is shown independent of the
* account type. If @a account does not have an @a id, all columns
* will be hidden.
*/
void setupRegister(const MyMoneyAccount& account, bool showAccountColumn = false);
/**
* Show the columns contained in @a cols for @a account. @a account
* can be left empty ( MyMoneyAccount() ) e.g. for the search dialog.
*/
void setupRegister(const MyMoneyAccount& account, const QList<Column>& cols);
void setSortOrder(const QString& order);
const QList<TransactionSortField>& sortOrder() const {
return m_sortOrder;
}
TransactionSortField primarySortKey() const;
void sortItems();
/**
* This member returns the last visible column that is used by the register
* after it has been setup using setupRegister().
*
* @return last actively used column (base 0)
*/
Column lastCol() const {
return m_lastCol;
}
RegisterItem* firstItem() const;
RegisterItem* firstVisibleItem() const;
RegisterItem* nextItem(RegisterItem*) const;
RegisterItem* lastItem() const;
RegisterItem* lastVisibleItem() const;
RegisterItem* prevItem(RegisterItem*) const;
RegisterItem* itemAtRow(int row) const;
void resize(int col, bool force = false);
void forceUpdateLists() {
m_listsDirty = true;
}
void ensureItemVisible(RegisterItem* item);
void arrangeEditWidgets(QMap<QString, QWidget*>& editWidgets, Transaction* t);
void removeEditWidgets(QMap<QString, QWidget*>& editWidgets);
void tabOrder(QWidgetList& tabOrderWidgets, KMyMoneyRegister::Transaction* t) const;
int rowHeightHint() const;
void clearSelection();
/**
* This method creates a specific transaction according to the
* transaction passed in @a transaction.
*
* @param parent pointer to register where the created object should be added
* @param transaction the transaction which should be used to create the object
* @param split the split of the transaction which should be used to create the object
* @param uniqueId an int that will be used to construct the id of the item
*
* @return pointer to created object (0 upon failure)
*/
static Transaction* transactionFactory(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId);
const MyMoneyAccount& account() const {
return m_account;
}
/**
* This method creates group marker items and adds them to the register
*/
void addGroupMarkers();
/**
* This method removes all trailing group markers and in a second
* run reduces all adjacent group markers to show only one. In that
* case the last one will remain.
*/
void removeUnwantedGroupMarkers();
void setLedgerLensForced(bool forced = true) {
m_ledgerLensForced = forced;
}
/**
* Sets the selection mode to @a mode. Supported modes are QTable::Single and
* QTable::Multi. QTable::Multi is the default when the object is created.
*/
void setSelectionMode(SelectionMode mode) {
m_selectionMode = mode;
}
/**
* This method sets a hint that the register instance will be used
* with a transaction editor. This information is used while the column
* sizes are being auto adjusted. If a transaction editor is used then
* it's possible that it will need some extra space.
*/
void setUsedWithEditor(bool value) {
m_usedWithEditor = value;
}
DetailsColumnType getDetailsColumnType() const;
void setDetailsColumnType(DetailsColumnType detailsColumnType);
protected:
void mouseReleaseEvent(QMouseEvent *e);
void contextMenuEvent(QContextMenuEvent *e);
void unselectItems(int from = -1, int to = -1) {
doSelectItems(from, to, false);
}
void selectItems(int from, int to) {
doSelectItems(from, to, true);
}
void doSelectItems(int from, int to, bool selected);
int selectedItemsCount() const;
bool event(QEvent*);
void focusOutEvent(QFocusEvent*);
void focusInEvent(QFocusEvent*);
void keyPressEvent(QKeyEvent*);
virtual void resizeEvent(QResizeEvent* ev);
int rowToIndex(int row) const;
void setupItemIndex(int rowCount);
/**
* This method determines the register item that is one page
* further down or up in the ledger from the previous focus item.
* The height to scroll is determined by visibleHeight()
*
* @param key Qt::Page_Up or Qt::Page_Down depending on the direction to scroll
* @param modifiers state of Qt::ShiftModifier, Qt::ControlModifier, Qt::AltModifier and
* Qt::MetaModifier.
*/
void scrollPage(int key, Qt::KeyboardModifiers modifiers);
/**
* This method determines the pointer to a RegisterItem
* based on the item's @a id. If @a id is empty, this method
* returns @a m_lastItem.
*
* @param id id of the item to be searched
* @return pointer to RegisterItem or 0 if not found
*/
RegisterItem* itemById(const QString& id) const;
/**
* Override logic and use standard QFrame behaviour
*/
bool focusNextPrevChild(bool next);
bool eventFilter(QObject* o, QEvent* e);
void handleItemChange(RegisterItem* old, bool shift, bool control);
void selectRange(RegisterItem* from, RegisterItem* to, bool invert, bool includeFirst, bool clearSel);
/**
* Returns the minimum column width based on the data in the header and the transactions.
*/
int minimumColumnWidth(int col);
protected slots:
void resize();
void selectItem(int row, int col);
void slotEnsureItemVisible();
void slotDoubleClicked(int row, int);
signals:
void transactionsSelected(const KMyMoneyRegister::SelectedTransactions& list);
/**
* This signal is emitted when the focus and selection changes to @p item.
*
* @param item pointer to transaction that received the focus and was selected
*/
void focusChanged(KMyMoneyRegister::Transaction* item);
/**
* This signal is emitted when the focus changes but the selection remains
* the same. This usually happens when the focus is changed using the keyboard.
*/
void focusChanged();
/**
* This signal is emitted when an @p item is about to be selected. The boolean
* @p okToSelect is preset to @c true. If the @p item should not be selected
* for whatever reason, the boolean @p okToSelect should be reset to @c false
* by the connected slot.
*/
void aboutToSelectItem(KMyMoneyRegister::RegisterItem* item, bool& okToSelect);
void editTransaction();
/**
* This signal is sent out when the user clicks on the ReconcileStateColumn and
* only a single transaction is selected.
*/
void reconcileStateColumnClicked(KMyMoneyRegister::Transaction* item);
/**
* This signal is sent out, if an item without a transaction id has been selected.
*/
void emptyItemSelected();
/**
* This signal is sent out, if the user selects an item with the right mouse button
*/
void openContextMenu();
/**
* This signal is sent out when a new item has been added to the register
*/
void itemAdded(RegisterItem* item);
protected:
ItemPtrVector m_items;
QVector<RegisterItem*> m_itemIndex;
RegisterItem* m_selectAnchor;
RegisterItem* m_focusItem;
RegisterItem* m_ensureVisibleItem;
RegisterItem* m_firstItem;
RegisterItem* m_lastItem;
RegisterItem* m_firstErroneous;
RegisterItem* m_lastErroneous;
int m_markErroneousTransactions;
int m_rowHeightHint;
MyMoneyAccount m_account;
bool m_ledgerLensForced;
SelectionMode m_selectionMode;
private:
bool m_needResize;
bool m_listsDirty;
bool m_ignoreNextButtonRelease;
bool m_needInitialColumnResize;
bool m_usedWithEditor;
Qt::MouseButtons m_mouseButton;
Qt::KeyboardModifiers m_modifiers;
Column m_lastCol;
QList<TransactionSortField> m_sortOrder;
QRect m_lastRepaintRect;
DetailsColumnType m_detailsColumnType;
};
} // namespace
#endif
diff --git a/kmymoney/widgets/transaction.cpp b/kmymoney/widgets/transaction.cpp
index 0b805e841..d46054b7d 100644
--- a/kmymoney/widgets/transaction.cpp
+++ b/kmymoney/widgets/transaction.cpp
@@ -1,2198 +1,2198 @@
/***************************************************************************
transaction.cpp - description
-------------------
begin : Tue Jun 13 2006
copyright : (C) 2000-2006 by Thomas Baumgart <ipwizard@users.sourceforge.net>
(C) 2017 by Łukasz Wojniłowicz <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 "transaction.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QString>
#include <QPainter>
#include <QWidget>
#include <QList>
#include <QPixmap>
#include <QBoxLayout>
#include <QHeaderView>
#include <QApplication>
#include <QTextDocument>
#include <QAbstractTextDocumentLayout>
#include <QPushButton>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyutils.h"
#include "mymoneytransaction.h"
#include "mymoneysplit.h"
#include "mymoneyfile.h"
#include "mymoneypayee.h"
#include "mymoneytag.h"
#include "register.h"
#include "kmymoneycategory.h"
#include "kmymoneydateinput.h"
#include "transactionform.h"
#include "kmymoneylineedit.h"
#include "kmymoneyedit.h"
#include "transactioneditor.h"
#include "investtransactioneditor.h"
#include "kmymoneyutils.h"
#include "kmymoneymvccombo.h"
#include "kmymoneyglobalsettings.h"
using namespace KMyMoneyRegister;
using namespace KMyMoneyTransactionForm;
static unsigned char attentionSign[] = {
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
0x08, 0x06, 0x00, 0x00, 0x00, 0x8D, 0x89, 0x1D,
0x0D, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49,
0x54, 0x08, 0x08, 0x08, 0x08, 0x7C, 0x08, 0x64,
0x88, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58,
0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72,
0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E,
0x6B, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2E, 0x6F,
0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00,
0x02, 0x05, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8D,
0xAD, 0x95, 0xBF, 0x4B, 0x5B, 0x51, 0x14, 0xC7,
0x3F, 0x2F, 0xBC, 0x97, 0x97, 0x97, 0x97, 0x77,
0xF3, 0xF2, 0x1C, 0xA4, 0x54, 0x6B, 0x70, 0x10,
0x44, 0x70, 0x2A, 0x91, 0x2E, 0x52, 0x02, 0x55,
0x8A, 0xB5, 0xA3, 0xAB, 0x38, 0x08, 0x66, 0xCC,
0xEE, 0xE0, 0xE2, 0x20, 0xB8, 0x38, 0xB8, 0xB8,
0xF8, 0x1F, 0x38, 0x29, 0xA5, 0x29, 0x74, 0x90,
0x0E, 0x0D, 0x0E, 0x22, 0x1D, 0x44, 0xA8, 0xD0,
0xD4, 0xB4, 0x58, 0x4B, 0x09, 0xF9, 0xF1, 0x4A,
0x3B, 0xD4, 0xD3, 0xE1, 0x55, 0xD3, 0x34, 0xAF,
0x49, 0x6C, 0x3D, 0xF0, 0x85, 0x7B, 0xCF, 0xFD,
0x9E, 0xEF, 0x3D, 0xE7, 0xFE, 0xD4, 0x44, 0x84,
0xDB, 0xB4, 0x48, 0x2F, 0xA4, 0x94, 0xAB, 0xE5,
0x52, 0xAE, 0x96, 0xEB, 0x49, 0x51, 0x44, 0x3A,
0x02, 0x18, 0x88, 0xC7, 0xF1, 0xE3, 0x71, 0x7C,
0x60, 0xA0, 0x1B, 0xBF, 0x6B, 0x86, 0x49, 0xC5,
0x46, 0x3E, 0x47, 0x34, 0x9F, 0x23, 0x9A, 0x54,
0x6C, 0xFC, 0x57, 0x86, 0x40, 0xC6, 0x4B, 0xE1,
0x37, 0xCA, 0x48, 0xA3, 0x8C, 0x78, 0x29, 0x7C,
0x20, 0xD3, 0x31, 0xA6, 0xD3, 0xA0, 0x52, 0x1C,
0x6D, 0x6F, 0x72, 0xD9, 0x28, 0x23, 0xFE, 0x07,
0x64, 0x7B, 0x93, 0x4B, 0xA5, 0x38, 0xFA, 0x27,
0x41, 0x60, 0x6E, 0x74, 0x84, 0x7A, 0xE5, 0x1D,
0x92, 0x54, 0x88, 0xE7, 0x22, 0xD5, 0x12, 0x32,
0x3A, 0x42, 0x1D, 0x98, 0xBB, 0x91, 0x20, 0x60,
0xDA, 0x36, 0x17, 0xFB, 0x7B, 0xC8, 0xC1, 0x4B,
0x04, 0x02, 0xBC, 0x7E, 0x81, 0xEC, 0xEF, 0x21,
0xB6, 0xCD, 0x05, 0x60, 0xF6, 0x2C, 0x68, 0x9A,
0x2C, 0xCF, 0x4C, 0xE1, 0x4B, 0x05, 0x39, 0x3F,
0x69, 0x0A, 0xBE, 0x7F, 0x83, 0x48, 0x05, 0x99,
0x99, 0xC2, 0x37, 0x4D, 0x96, 0x7B, 0x12, 0x04,
0xFA, 0x2D, 0x8B, 0xC6, 0xE9, 0x61, 0x10, 0x2C,
0x15, 0xC4, 0x8A, 0x21, 0x86, 0x8E, 0xFC, 0xF8,
0x12, 0xF4, 0x4F, 0x0F, 0x11, 0xCB, 0xA2, 0x01,
0xF4, 0x77, 0x3D, 0x36, 0x4E, 0x82, 0xF5, 0xA5,
0x05, 0x8C, 0xE1, 0x74, 0xD3, 0x37, 0x34, 0x18,
0x20, 0xF2, 0x8B, 0x3D, 0x9C, 0x86, 0xA5, 0x05,
0x0C, 0x27, 0xC1, 0x7A, 0xC7, 0x63, 0x03, 0x8C,
0x2B, 0x07, 0xBF, 0x5A, 0x6A, 0x66, 0x27, 0x15,
0x64, 0x3A, 0x8B, 0x3C, 0x7A, 0xD8, 0xEA, 0xAB,
0x96, 0x10, 0xE5, 0xE0, 0x03, 0xE3, 0x7F, 0xCD,
0x50, 0x39, 0x6C, 0xAD, 0xAD, 0x10, 0x53, 0xAA,
0x75, 0xD2, 0xF4, 0xBD, 0x00, 0x2D, 0x5C, 0x05,
0x6B, 0x2B, 0xC4, 0x94, 0xC3, 0xD6, 0xEF, 0xFE,
0x6B, 0x41, 0x4D, 0xD3, 0x66, 0xFB, 0x3C, 0xC6,
0x16, 0xE7, 0xDB, 0x97, 0x61, 0xE2, 0x3E, 0x3C,
0xC8, 0xB4, 0x15, 0xC7, 0xE2, 0x3C, 0x91, 0x3E,
0x8F, 0x31, 0x4D, 0xD3, 0x66, 0x5B, 0x4A, 0x06,
0x8C, 0x84, 0xCD, 0x59, 0x61, 0xA7, 0xB5, 0xAC,
0x2B, 0x9C, 0x1C, 0x04, 0x08, 0x1B, 0x2B, 0xEC,
0x20, 0x09, 0x9B, 0x33, 0xC0, 0xB8, 0xDE, 0x65,
0x43, 0x27, 0x9F, 0x9D, 0xA4, 0x1E, 0x16, 0xF0,
0xF9, 0x6D, 0xB0, 0xC3, 0x86, 0x1E, 0xB4, 0xC3,
0x38, 0xD9, 0x49, 0xEA, 0x86, 0x4E, 0xFE, 0xEA,
0x29, 0xF4, 0x2C, 0x8B, 0xDA, 0x71, 0x31, 0x9C,
0xFC, 0xF5, 0x23, 0x32, 0x34, 0x88, 0xDC, 0xBD,
0x13, 0x5C, 0xBF, 0x30, 0xCE, 0x71, 0x11, 0xB1,
0x2C, 0x6A, 0x80, 0xA7, 0xDB, 0x36, 0xAB, 0x4F,
0xA6, 0x89, 0xBA, 0x49, 0x38, 0xFF, 0xD4, 0xBE,
0x4E, 0x00, 0xAF, 0x9E, 0x81, 0x08, 0xD4, 0xEA,
0x01, 0xFE, 0x34, 0x37, 0x09, 0x4F, 0x1F, 0x13,
0xDD, 0x7D, 0xCE, 0xAA, 0x96, 0x72, 0x29, 0x7C,
0xFB, 0xCE, 0x44, 0xB8, 0xD4, 0xCD, 0x2C, 0x66,
0x52, 0xD4, 0x6E, 0xFB, 0x0B, 0xF8, 0x09, 0x63,
0x63, 0x31, 0xE4, 0x85, 0x76, 0x2E, 0x0E, 0x00,
0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
0x42, 0x60, 0x82
};
Transaction::Transaction(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) :
RegisterItem(parent),
m_transaction(transaction),
m_split(split),
m_form(0),
m_uniqueId(m_transaction.id()),
m_formRowHeight(-1),
m_selected(false),
m_focus(false),
m_erroneous(false),
m_inEdit(false),
m_inRegisterEdit(false),
m_showBalance(true),
m_reducedIntensity(false)
{
MyMoneyFile* file = MyMoneyFile::instance();
// load the account
if (!m_split.accountId().isEmpty())
m_account = file->account(m_split.accountId());
// load the payee
if (!m_split.payeeId().isEmpty()) {
m_payee = file->payee(m_split.payeeId()).name();
}
if (parent->account().isIncomeExpense()) {
m_payeeHeader = m_split.shares().isNegative() ? i18n("From") : i18n("Pay to");
} else {
m_payeeHeader = m_split.shares().isNegative() ? i18n("Pay to") : i18n("From");
}
// load the tag
if (!m_split.tagIdList().isEmpty()) {
const QList<QString> t = m_split.tagIdList();
for (int i = 0; i < t.count(); i++) {
m_tagList << file->tag(t[i]).name();
m_tagColorList << file->tag(t[i]).tagColor();
}
}
// load the currency
if (!m_transaction.id().isEmpty())
m_splitCurrencyId = m_account.currencyId();
// check if transaction is erroneous or not
m_erroneous = !m_transaction.splitSum().isZero();
if (!m_uniqueId.isEmpty()) {
m_uniqueId += '-';
QString id;
id.setNum(uniqueId);
m_uniqueId += id.rightJustified(3, '0');
}
}
void Transaction::setFocus(bool focus, bool updateLens)
{
if (focus != m_focus) {
m_focus = focus;
}
if (updateLens) {
if (KMyMoneyGlobalSettings::ledgerLens()
|| !KMyMoneyGlobalSettings::transactionForm()
|| KMyMoneyGlobalSettings::showRegisterDetailed()
|| m_parent->m_ledgerLensForced) {
if (focus)
setNumRowsRegister(numRowsRegister(true));
else
setNumRowsRegister(numRowsRegister(KMyMoneyGlobalSettings::showRegisterDetailed()));
}
}
}
bool Transaction::paintRegisterCellSetup(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index)
{
Q_UNUSED(painter)
if (m_reducedIntensity) {
option.palette.setColor(QPalette::Text, option.palette.color(QPalette::Disabled, QPalette::Text));
}
if (m_selected) {
option.state |= QStyle::State_Selected;
} else {
option.state &= ~QStyle::State_Selected;
}
if (m_focus) {
option.state |= QStyle::State_HasFocus;
} else {
option.state &= ~QStyle::State_HasFocus;
}
if (option.widget && option.widget->hasFocus()) {
option.palette.setCurrentColorGroup(QPalette::Active);
} else {
option.palette.setCurrentColorGroup(QPalette::Inactive);
}
if (index.column() == 0) {
option.viewItemPosition = QStyleOptionViewItem::Beginning;
} else if (index.column() == MaxColumns - 1) {
option.viewItemPosition = QStyleOptionViewItem::End;
} else {
option.viewItemPosition = QStyleOptionViewItem::Middle;
}
// do we need to switch to the error color?
if (m_erroneous) {
option.palette.setColor(QPalette::Text, KMyMoneyGlobalSettings::schemeColor(SchemeColor::TransactionErroneous));
}
// do we need to switch to the negative balance color?
if (index.column() == BalanceColumn) {
bool showNegative = m_balance.isNegative();
- if (m_account.accountGroup() == MyMoneyAccount::Liability && !m_balance.isZero())
+ if (m_account.accountGroup() == eMyMoney::Account::Liability && !m_balance.isZero())
showNegative = !showNegative;
if (showNegative)
option.palette.setColor(QPalette::Text, KMyMoneyGlobalSettings::schemeColor(SchemeColor::TransactionErroneous));
}
return true;
}
void Transaction::registerCellText(QString& txt, int row, int col)
{
Qt::Alignment align;
registerCellText(txt, align, row, col, 0);
}
void Transaction::paintRegisterCell(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index)
{
painter->save();
if (paintRegisterCellSetup(painter, option, index)) {
const QStyle *style = option.widget ? option.widget->style() : QApplication::style();
const QWidget* widget = option.widget;
// clear the mouse over state before painting the background
option.state &= ~QStyle::State_MouseOver;
// the background
if (option.state & QStyle::State_Selected || option.state & QStyle::State_HasFocus) {
// if this is not the first row of the transaction paint the previous rows
// since the selection background is painted from the first row of the transaction
if (index.row() > startRow()) {
QStyleOptionViewItem optionSibling = option;
QModelIndex previousRowItem = index.sibling(index.row() - 1, index.column());
optionSibling.rect = m_parent->visualRect(previousRowItem);
paintRegisterCell(painter, optionSibling, previousRowItem);
}
// paint the selection background only from the first row on to the last row at once
if (index.row() == startRow()) {
QRect old = option.rect;
int extraHeight = 0;
if (m_inRegisterEdit) {
// since, when editing a transaction inside the register (without the transaction form),
// row heights can have various sizes (the memo row is larger than the rest) we have
// to iterate over all the items of the transaction to compute the size of the selection rectangle
// of course we start with the item after this one because it's size is already in the rectangle
for (int i = startRow() + 1; i < startRow() + numRowsRegister(); ++i) {
extraHeight += m_parent->visualRect(index.sibling(i, index.column())).height();
}
} else {
// we are not editing in the register so all rows have the same sizes just compute the extra height
extraHeight = (numRowsRegister() - 1) * option.rect.height();
}
option.rect.setBottom(option.rect.bottom() + extraHeight);
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, widget);
if (m_focus && index.column() == DetailColumn) {
option.state |= QStyle::State_HasFocus;
style->drawPrimitive(QStyle::PE_FrameFocusRect, &option, painter, widget);
}
option.rect = old;
}
} else {
if (m_alternate) {
painter->fillRect(option.rect, option.palette.alternateBase());
} else {
painter->fillRect(option.rect, option.palette.base());
}
}
// the text
// construct the text for the cell
QString txt;
option.displayAlignment = Qt::AlignVCenter;
if (m_transaction != MyMoneyTransaction() && !m_inRegisterEdit) {
registerCellText(txt, option.displayAlignment, index.row() - startRow(), index.column(), painter);
}
if (Qt::mightBeRichText(txt)) {
QTextDocument document;
// this should set the alignment of the html but it does not work so htmls will be left aligned
document.setDefaultTextOption(QTextOption(option.displayAlignment));
document.setDocumentMargin(2);
document.setHtml(txt);
painter->translate(option.rect.topLeft());
QAbstractTextDocumentLayout::PaintContext ctx;
ctx.palette = option.palette;
// Highlighting text if item is selected
if (m_selected)
ctx.palette.setColor(QPalette::Text, option.palette.color(QPalette::HighlightedText));
document.documentLayout()->draw(painter, ctx);
painter->translate(-option.rect.topLeft());
} else {
// draw plain text properly aligned
style->drawItemText(painter, option.rect.adjusted(2, 0, -2, 0), option.displayAlignment, option.palette, true, txt, m_selected ? QPalette::HighlightedText : QPalette::Text);
}
// draw the grid if it's needed
if (KMyMoneySettings::showGrid()) {
const int gridHint = style->styleHint(QStyle::SH_Table_GridLineColor, &option, widget);
const QPen gridPen = QPen(QColor(static_cast<QRgb>(gridHint)), 0);
QPen old = painter->pen();
painter->setPen(gridPen);
if (index.row() == startRow())
painter->drawLine(option.rect.topLeft(), option.rect.topRight());
painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft());
painter->setPen(old);
}
// possible icons
if (index.row() == startRow() && index.column() == DetailColumn) {
if (m_erroneous) {
QPixmap attention;
attention.loadFromData(attentionSign, sizeof(attentionSign), 0, 0);
style->drawItemPixmap(painter, option.rect, Qt::AlignRight | Qt::AlignVCenter, attention);
}
}
}
painter->restore();
}
int Transaction::formRowHeight(int /*row*/)
{
if (m_formRowHeight < 0) {
m_formRowHeight = formRowHeight();
}
return m_formRowHeight;
}
int Transaction::formRowHeight() const
{
if (m_formRowHeight < 0) {
// determine the height of the objects in the table
kMyMoneyDateInput dateInput;
KMyMoneyCategory category(0, true);
return qMax(dateInput.sizeHint().height(), category.sizeHint().height());
}
return m_formRowHeight;
}
void Transaction::setupForm(TransactionForm* form)
{
m_form = form;
form->verticalHeader()->setUpdatesEnabled(false);
form->horizontalHeader()->setUpdatesEnabled(false);
form->setRowCount(numRowsForm());
form->setColumnCount(numColsForm());
// Force all cells to have some text (so that paintCell is called for each cell)
for (int r = 0; r < numRowsForm(); ++r) {
for (int c = 0; c < numColsForm(); ++c) {
if (r == 0 && form->columnWidth(c) == 0) {
form->setColumnWidth(c, 10);
}
}
}
form->horizontalHeader()->setUpdatesEnabled(true);
form->verticalHeader()->setUpdatesEnabled(true);
loadTab(form);
}
void Transaction::paintFormCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
{
if (!m_form)
return;
QRect cellRect = option.rect;
QRect textRect(cellRect);
textRect.setWidth(textRect.width() - 2);
textRect.setHeight(textRect.height() - 2);
painter->setPen(option.palette.text().color());
QString txt;
Qt::Alignment align = Qt::AlignVCenter;
bool editField = formCellText(txt, align, index.row(), index.column(), painter);
// if we have an editable field and don't currently edit the transaction
// show the background in a different color
if (editField && !m_inEdit) {
painter->fillRect(textRect, option.palette.alternateBase());
}
if (!m_inEdit)
painter->drawText(textRect, align, txt);
}
void Transaction::setupPalette(const QPalette& palette, QMap<QString, QWidget*>& editWidgets)
{
QMap<QString, QWidget*>::iterator it_w;
for (it_w = editWidgets.begin(); it_w != editWidgets.end(); ++it_w) {
if (*it_w) {
(*it_w)->setPalette(palette);
}
}
}
void Transaction::setupFormPalette(QMap<QString, QWidget*>& editWidgets)
{
QPalette palette = m_parent->palette();
palette.setColor(QPalette::Active, QPalette::Base, palette.color(QPalette::Active, QPalette::Base));
setupPalette(palette, editWidgets);
}
void Transaction::setupRegisterPalette(QMap<QString, QWidget*>& editWidgets)
{
// make sure, we're using the right palette
QPalette palette = m_parent->palette();
// use the highlight color as background
palette.setColor(QPalette::Active, QPalette::Background, palette.color(QPalette::Active, QPalette::Highlight));
setupPalette(palette, editWidgets);
}
QWidget* Transaction::focusWidget(QWidget* w) const
{
if (w) {
while (w->focusProxy())
w = w->focusProxy();
}
return w;
}
void Transaction::arrangeWidget(QTableWidget* tbl, int row, int col, QWidget* w) const
{
if (w) {
tbl->setCellWidget(row, col, w);
// remove the widget from the QTable's eventFilter so that all
// events will be directed to the edit widget
w->removeEventFilter(tbl);
}
}
bool Transaction::haveNumberField() const
{
bool rc = true;
switch (m_account.accountType()) {
- case MyMoneyAccount::Savings:
- case MyMoneyAccount::Cash:
- case MyMoneyAccount::Loan:
- case MyMoneyAccount::AssetLoan:
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Equity:
+ case eMyMoney::Account::Savings:
+ case eMyMoney::Account::Cash:
+ case eMyMoney::Account::Loan:
+ case eMyMoney::Account::AssetLoan:
+ case eMyMoney::Account::Asset:
+ case eMyMoney::Account::Liability:
+ case eMyMoney::Account::Equity:
rc = KMyMoneyGlobalSettings::alwaysShowNrField();
break;
- case MyMoneyAccount::Checkings:
- case MyMoneyAccount::CreditCard:
+ case eMyMoney::Account::Checkings:
+ case eMyMoney::Account::CreditCard:
// the next case is used for the editor when the account
// is unknown (eg. when creating new schedules)
- case MyMoneyAccount::UnknownAccountType:
+ case eMyMoney::Account::Unknown:
break;
default:
rc = false;
break;
}
return rc;
}
bool Transaction::maybeTip(const QPoint& cpos, int row, int col, QRect& r, QString& msg)
{
if (col != DetailColumn)
return false;
if (!m_erroneous && m_transaction.splitCount() < 3)
return false;
// check for detail column in row 0 of the transaction for a possible
// exclamation mark. m_startRow is based 0, whereas the row to obtain
// the modelindex is based 1, so we need to add one here
r = m_parent->visualRect(m_parent->model()->index(m_startRow + 1, col));
r.setBottom(r.bottom() + (numRowsRegister() - 1)*r.height());
if (r.contains(cpos) && m_erroneous) {
if (m_transaction.splits().count() < 2) {
msg = QString("<qt>%1</qt>").arg(i18n("Transaction is missing a category assignment."));
} else {
const MyMoneySecurity& sec = MyMoneyFile::instance()->security(m_account.currencyId());
msg = QString("<qt>%1</qt>").arg(i18n("The transaction has a missing assignment of <b>%1</b>.", MyMoneyUtils::formatMoney(m_transaction.splitSum().abs(), m_account, sec)));
}
return true;
}
// check if the mouse cursor is located on row 1 of the transaction
// and display the details of a split transaction if it is one
if (row == 1 && r.contains(cpos) && m_transaction.splitCount() > 2) {
MyMoneyFile* file = MyMoneyFile::instance();
QList<MyMoneySplit>::const_iterator it_s;
QString txt;
const MyMoneySecurity& sec = file->security(m_transaction.commodity());
MyMoneyMoney factor(1, 1);
if (!m_split.value().isNegative())
factor = -factor;
for (it_s = m_transaction.splits().constBegin(); it_s != m_transaction.splits().constEnd(); ++it_s) {
if (*it_s == m_split)
continue;
const MyMoneyAccount& acc = file->account((*it_s).accountId());
QString category = file->accountToCategory(acc.id());
QString amount = MyMoneyUtils::formatMoney(((*it_s).value() * factor), acc, sec);
txt += QString("<tr><td><nobr>%1</nobr></td><td align=right><nobr>%2</nobr></td></tr>").arg(category, amount);
}
msg = QString("<table>%1</table>").arg(txt);
return true;
}
return false;
}
QString Transaction::reconcileState(bool text) const
{
QString txt = KMyMoneyUtils::reconcileStateToString(m_split.reconcileFlag(), text);
if ((text == true)
&& (txt == i18nc("Unknown reconciliation state", "Unknown"))
&& (m_transaction == MyMoneyTransaction()))
txt.clear();
return txt;
}
void Transaction::startEditMode()
{
m_inEdit = true;
// hide the original tabbar since the edit tabbar will be added
KMyMoneyTransactionForm::TransactionForm* form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(m_form);
form->tabBar()->setVisible(false);
// only update the number of lines displayed if we edit inside the register
if (m_inRegisterEdit)
setNumRowsRegister(numRowsRegister(true));
}
void Transaction::leaveEditMode()
{
// show the original tabbar since the edit tabbar was removed
KMyMoneyTransactionForm::TransactionForm* form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(m_form);
form->tabBar()->setVisible(true);
// make sure we reset the row height of all the transaction's rows because it could have been changed during edit
if (m_parent) {
for (int i = 0; i < numRowsRegister(); ++i)
m_parent->setRowHeight(m_startRow + i, m_parent->rowHeightHint());
}
m_inEdit = false;
m_inRegisterEdit = false;
setFocus(hasFocus(), true);
}
void Transaction::singleLineMemo(QString& txt, const MyMoneySplit& split) const
{
txt = split.memo();
// remove empty lines
txt.replace("\n\n", "\n");
// replace '\n' with ", "
txt.replace('\n', ", ");
}
int Transaction::rowHeightHint() const
{
return m_inEdit ? formRowHeight() : RegisterItem::rowHeightHint();
}
bool Transaction::matches(const RegisterFilter& filter) const
{
// check if the state matches
if (!transaction().id().isEmpty()) {
switch (filter.state) {
default:
break;
case RegisterFilter::Imported:
if (!transaction().isImported())
return false;
break;
case RegisterFilter::Matched:
if (!split().isMatched())
return false;
break;
case RegisterFilter::Erroneous:
if (transaction().splitSum().isZero())
return false;
break;
case RegisterFilter::NotMarked:
if (split().reconcileFlag() != MyMoneySplit::NotReconciled)
return false;
break;
case RegisterFilter::NotReconciled:
if (split().reconcileFlag() != MyMoneySplit::NotReconciled
&& split().reconcileFlag() != MyMoneySplit::Cleared)
return false;
break;
case RegisterFilter::Cleared:
if (split().reconcileFlag() != MyMoneySplit::Cleared)
return false;
break;
}
}
// check if the text matches
if (filter.text.isEmpty() || m_transaction.splitCount() == 0)
return true;
MyMoneyFile* file = MyMoneyFile::instance();
const QList<MyMoneySplit>&list = m_transaction.splits();
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = list.begin(); it_s != list.end(); ++it_s) {
// check if the text is contained in one of the fields
// memo, number, payee, tag, account
if ((*it_s).memo().contains(filter.text, Qt::CaseInsensitive)
|| (*it_s).number().contains(filter.text, Qt::CaseInsensitive))
return true;
if (!(*it_s).payeeId().isEmpty()) {
const MyMoneyPayee& payee = file->payee((*it_s).payeeId());
if (payee.name().contains(filter.text, Qt::CaseInsensitive))
return true;
}
if (!(*it_s).tagIdList().isEmpty()) {
const QList<QString>& t = (*it_s).tagIdList();
for (int i = 0; i < t.count(); i++) {
if ((file->tag(t[i])).name().contains(filter.text, Qt::CaseInsensitive))
return true;
}
}
const MyMoneyAccount& acc = file->account((*it_s).accountId());
if (acc.name().contains(filter.text, Qt::CaseInsensitive))
return true;
QString s(filter.text);
s.replace(MyMoneyMoney::thousandSeparator(), QChar());
if (!s.isEmpty()) {
// check if any of the value field matches if a value has been entered
QString r = (*it_s).value().formatMoney(m_account.fraction(), false);
if (r.contains(s, Qt::CaseInsensitive))
return true;
const MyMoneyAccount& acc = file->account((*it_s).accountId());
r = (*it_s).shares().formatMoney(acc.fraction(), false);
if (r.contains(s, Qt::CaseInsensitive))
return true;
}
}
return false;
}
void Transaction::setShowBalance(bool showBalance)
{
m_showBalance = showBalance;
}
void Transaction::setVisible(bool visible)
{
if (visible != isVisible()) {
RegisterItem::setVisible(visible);
RegisterItem* p;
Transaction* t;
if (!visible) {
// if we are hidden, we need to inform all previous transactions
// about it so that they don't show the balance
p = prevItem();
while (p) {
t = dynamic_cast<Transaction*>(p);
if (t) {
if (!t->m_showBalance)
break;
t->m_showBalance = false;
}
p = p->prevItem();
}
} else {
// if we are shown, we need to check if the next transaction
// is visible and change the display of the balance
p = this;
do {
p = p->nextItem();
t = dynamic_cast<Transaction*>(p);
} while (!t && p);
// if the next transaction is visible or I am the last one
if ((t && t->m_showBalance) || !t) {
m_showBalance = true;
p = prevItem();
while (p && p->isVisible()) {
t = dynamic_cast<Transaction*>(p);
if (t) {
if (t->m_showBalance)
break;
t->m_showBalance = true;
}
p = p->prevItem();
}
}
}
}
}
void Transaction::setSelected(bool selected)
{
if (!selected || (selected && isVisible()))
m_selected = selected;
}
StdTransaction::StdTransaction(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) :
Transaction(parent, transaction, split, uniqueId),
m_showAccountRow(false)
{
try {
m_categoryHeader = i18n("Category");
switch (transaction.splitCount()) {
default:
m_category = i18nc("Split transaction (category replacement)", "Split transaction");
break;
case 0: // the empty transaction
case 1:
break;
case 2:
setupFormHeader(m_transaction.splitByAccount(m_split.accountId(), false).accountId());
break;
}
} catch (const MyMoneyException &e) {
qDebug() << "Problem determining the category for transaction '" << m_transaction.id() << "'. Reason: " << e.what() << "\n";
}
m_rowsForm = 6;
if (KMyMoneyUtils::transactionType(m_transaction) == KMyMoneyUtils::InvestmentTransaction) {
MyMoneySplit split = KMyMoneyUtils::stockSplit(m_transaction);
m_payee = MyMoneyFile::instance()->account(split.accountId()).name();
QString addon;
if (split.action() == MyMoneySplit::ActionBuyShares) {
if (split.value().isNegative()) {
addon = i18n("Sell");
} else {
addon = i18n("Buy");
}
} else if (split.action() == MyMoneySplit::ActionDividend) {
addon = i18n("Dividend");
} else if (split.action() == MyMoneySplit::ActionYield) {
addon = i18n("Yield");
} else if (split.action() == MyMoneySplit::ActionInterestIncome) {
addon = i18n("Interest Income");
}
if (!addon.isEmpty()) {
m_payee += QString(" (%1)").arg(addon);
}
m_payeeHeader = i18n("Activity");
m_category = i18n("Investment transaction");
}
// setup initial size
setNumRowsRegister(numRowsRegister(KMyMoneyGlobalSettings::showRegisterDetailed()));
emit parent->itemAdded(this);
}
void StdTransaction::setupFormHeader(const QString& id)
{
m_category = MyMoneyFile::instance()->accountToCategory(id);
switch (MyMoneyFile::instance()->account(id).accountGroup()) {
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::Liability:
+ case eMyMoney::Account::Asset:
+ case eMyMoney::Account::Liability:
m_categoryHeader = m_split.shares().isNegative() ? i18n("Transfer to") : i18n("Transfer from");
break;
default:
m_categoryHeader = i18n("Category");
break;
}
}
KMyMoneyRegister::Action StdTransaction::actionType() const
{
KMyMoneyRegister::Action action = ActionNone;
// if at least one split is referencing an income or
// expense account, we will not call it a transfer
QList<MyMoneySplit>::const_iterator it_s;
for (it_s = m_transaction.splits().begin(); it_s != m_transaction.splits().end(); ++it_s) {
if ((*it_s).accountId() == m_split.accountId())
continue;
MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
- if (acc.accountGroup() == MyMoneyAccount::Income
- || acc.accountGroup() == MyMoneyAccount::Expense) {
+ if (acc.accountGroup() == eMyMoney::Account::Income
+ || acc.accountGroup() == eMyMoney::Account::Expense) {
// otherwise, we have to determine between deposit and withdrawal
action = m_split.shares().isNegative() ? ActionWithdrawal : ActionDeposit;
break;
}
}
// otherwise, it's a transfer
if (it_s == m_transaction.splits().end())
action = ActionTransfer;
return action;
}
void StdTransaction::loadTab(TransactionForm* form)
{
TabBar* bar = form->tabBar();
bar->setSignalEmission(TabBar::SignalNever);
for (int i = 0; i < bar->count(); ++i) {
bar->setTabEnabled(i, true);
}
if (m_transaction.splitCount() > 0) {
bar->setCurrentIndex(actionType());
}
bar->setSignalEmission(TabBar::SignalAlways);
}
void StdTransaction::setupForm(TransactionForm* form)
{
Transaction::setupForm(form);
form->setSpan(4, ValueColumn1, 3, 1);
}
bool StdTransaction::showRowInForm(int row) const
{
return row == 0 ? m_showAccountRow : true;
}
void StdTransaction::setShowRowInForm(int row, bool show)
{
if (row == 0)
m_showAccountRow = show;
}
bool StdTransaction::formCellText(QString& txt, Qt::Alignment& align, int row, int col, QPainter* /* painter */)
{
// if(m_transaction != MyMoneyTransaction()) {
switch (row) {
case 0:
switch (col) {
case LabelColumn1:
align |= Qt::AlignLeft;
txt = i18n("Account");
break;
}
break;
case 1:
switch (col) {
case LabelColumn1:
align |= Qt::AlignLeft;
txt = m_payeeHeader;
break;
case ValueColumn1:
align |= Qt::AlignLeft;
txt = m_payee;
break;
case LabelColumn2:
align |= Qt::AlignLeft;
if (haveNumberField())
txt = i18n("Number");
break;
case ValueColumn2:
align |= Qt::AlignRight;
if (haveNumberField())
txt = m_split.number();
break;
}
break;
case 2:
switch (col) {
case LabelColumn1:
align |= Qt::AlignLeft;
txt = m_categoryHeader;
break;
case ValueColumn1:
align |= Qt::AlignLeft;
txt = m_category;
if (m_transaction != MyMoneyTransaction()) {
if (txt.isEmpty() && !m_split.value().isZero())
txt = i18n("*** UNASSIGNED ***");
}
break;
case LabelColumn2:
align |= Qt::AlignLeft;
txt = i18n("Date");
break;
case ValueColumn2:
align |= Qt::AlignRight;
if (m_transaction != MyMoneyTransaction())
txt = QLocale().toString(m_transaction.postDate(), QLocale::ShortFormat);
break;
}
break;
case 3:
switch (col) {
case LabelColumn1:
align |= Qt::AlignLeft;
txt = i18n("Tags");
break;
case ValueColumn1:
align |= Qt::AlignLeft;
if (!m_tagList.isEmpty()) {
for (int i = 0; i < m_tagList.size() - 1; i++)
txt += m_tagList[i] + ", ";
txt += m_tagList.last();
}
//if (m_transaction != MyMoneyTransaction())
// txt = m_split.tagId();
break;
case LabelColumn2:
align |= Qt::AlignLeft;
txt = i18n("Amount");
break;
case ValueColumn2:
align |= Qt::AlignRight;
if (m_transaction != MyMoneyTransaction()) {
txt = (m_split.value(m_transaction.commodity(), m_splitCurrencyId).abs()).formatMoney(m_account.fraction());
}
break;
}
break;
case 4:
switch (col) {
case LabelColumn1:
align |= Qt::AlignLeft;
txt = i18n("Memo");
break;
case ValueColumn1:
align &= ~Qt::AlignVCenter;
align |= Qt::AlignTop;
align |= Qt::AlignLeft;
if (m_transaction != MyMoneyTransaction())
txt = m_split.memo().section('\n', 0, 2);
break;
}
break;
case 5:
switch (col) {
case LabelColumn2:
align |= Qt::AlignLeft;
txt = i18n("Status");
break;
case ValueColumn2:
align |= Qt::AlignRight;
txt = reconcileState();
break;
}
}
// }
if (col == ValueColumn2 && row == 1) {
return haveNumberField();
}
return (col == ValueColumn1 && row < 5) || (col == ValueColumn2 && row > 0 && row != 4);
}
void StdTransaction::registerCellText(QString& txt, Qt::Alignment& align, int row, int col, QPainter* painter)
{
switch (row) {
case 0:
switch (col) {
case NumberColumn:
align |= Qt::AlignLeft;
if (haveNumberField())
txt = m_split.number();
break;
case DateColumn:
align |= Qt::AlignLeft;
txt = QLocale().toString(m_transaction.postDate(), QLocale::ShortFormat);
break;
case DetailColumn:
switch (m_parent->getDetailsColumnType()) {
case PayeeFirst:
txt = m_payee;
break;
case AccountFirst:
txt = m_category;
if (!m_tagList.isEmpty()) {
txt += " ( ";
for (int i = 0; i < m_tagList.size() - 1; i++) {
txt += "<span style='color: " + m_tagColorList[i].name() + "'>&#x25CF;</span> " + m_tagList[i] + ", ";
}
txt += "<span style='color: " + m_tagColorList.last().name() + "'>&#x25CF;</span> " + m_tagList.last() + " )";
}
break;
}
align |= Qt::AlignLeft;
if (txt.isEmpty() && m_rowsRegister < 3) {
singleLineMemo(txt, m_split);
}
if (txt.isEmpty() && m_rowsRegister < 2) {
- if (m_account.accountType() != MyMoneyAccount::Income
- && m_account.accountType() != MyMoneyAccount::Expense) {
+ if (m_account.accountType() != eMyMoney::Account::Income
+ && m_account.accountType() != eMyMoney::Account::Expense) {
txt = m_category;
if (txt.isEmpty() && !m_split.value().isZero()) {
txt = i18n("*** UNASSIGNED ***");
if (painter)
painter->setPen(KMyMoneyGlobalSettings::schemeColor(SchemeColor::TransactionErroneous));
}
}
}
break;
case ReconcileFlagColumn:
align |= Qt::AlignHCenter;
txt = reconcileState(false);
break;
case PaymentColumn:
align |= Qt::AlignRight;
if (m_split.value().isNegative()) {
txt = (-m_split.value(m_transaction.commodity(), m_splitCurrencyId)).formatMoney(m_account.fraction());
}
break;
case DepositColumn:
align |= Qt::AlignRight;
if (!m_split.value().isNegative()) {
txt = m_split.value(m_transaction.commodity(), m_splitCurrencyId).formatMoney(m_account.fraction());
}
break;
case BalanceColumn:
align |= Qt::AlignRight;
if (m_showBalance)
txt = m_balance.formatMoney(m_account.fraction());
else
txt = "----";
break;
case AccountColumn:
// txt = m_objects->account(m_transaction.splits()[0].accountId()).name();
txt = MyMoneyFile::instance()->account(m_split.accountId()).name();
break;
default:
break;
}
break;
case 1:
switch (col) {
case DetailColumn:
switch (m_parent->getDetailsColumnType()) {
case PayeeFirst:
txt = m_category;
if (!m_tagList.isEmpty()) {
txt += " ( ";
for (int i = 0; i < m_tagList.size() - 1; i++) {
txt += "<span style='color: " + m_tagColorList[i].name() + "'>&#x25CF;</span> " + m_tagList[i] + ", ";
}
txt += "<span style='color: " + m_tagColorList.last().name() + "'>&#x25CF;</span> " + m_tagList.last() + " )";
}
break;
case AccountFirst:
txt = m_payee;
break;
}
align |= Qt::AlignLeft;
if (txt.isEmpty() && !m_split.value().isZero()) {
txt = i18n("*** UNASSIGNED ***");
if (painter)
painter->setPen(KMyMoneyGlobalSettings::schemeColor(SchemeColor::TransactionErroneous));
}
break;
default:
break;
}
break;
case 2:
switch (col) {
case DetailColumn:
align |= Qt::AlignLeft;
singleLineMemo(txt, m_split);
break;
default:
break;
}
break;
}
}
int StdTransaction::registerColWidth(int col, const QFontMetrics& cellFontMetrics)
{
QString txt;
int firstRow = 0, lastRow = numRowsRegister();
int nw = 0;
for (int i = firstRow; i <= lastRow; ++i) {
Qt::Alignment align;
registerCellText(txt, align, i, col, 0);
int w = cellFontMetrics.width(txt + " ");
if (w > nw)
nw = w;
}
return nw;
}
void StdTransaction::arrangeWidgetsInForm(QMap<QString, QWidget*>& editWidgets)
{
if (!m_form || !m_parent)
return;
setupFormPalette(editWidgets);
arrangeWidget(m_form, 0, LabelColumn1, editWidgets["account-label"]);
arrangeWidget(m_form, 0, ValueColumn1, editWidgets["account"]);
arrangeWidget(m_form, 1, LabelColumn1, editWidgets["cashflow"]);
arrangeWidget(m_form, 1, ValueColumn1, editWidgets["payee"]);
arrangeWidget(m_form, 2, LabelColumn1, editWidgets["category-label"]);
arrangeWidget(m_form, 2, ValueColumn1, editWidgets["category"]->parentWidget());
arrangeWidget(m_form, 3, LabelColumn1, editWidgets["tag-label"]);
arrangeWidget(m_form, 3, ValueColumn1, editWidgets["tag"]);
arrangeWidget(m_form, 4, LabelColumn1, editWidgets["memo-label"]);
arrangeWidget(m_form, 4, ValueColumn1, editWidgets["memo"]);
if (haveNumberField()) {
arrangeWidget(m_form, 1, LabelColumn2, editWidgets["number-label"]);
arrangeWidget(m_form, 1, ValueColumn2, editWidgets["number"]);
}
arrangeWidget(m_form, 2, LabelColumn2, editWidgets["date-label"]);
arrangeWidget(m_form, 2, ValueColumn2, editWidgets["postdate"]);
arrangeWidget(m_form, 3, LabelColumn2, editWidgets["amount-label"]);
arrangeWidget(m_form, 3, ValueColumn2, editWidgets["amount"]);
arrangeWidget(m_form, 5, LabelColumn2, editWidgets["status-label"]);
arrangeWidget(m_form, 5, ValueColumn2, editWidgets["status"]);
// get rid of the hints. we don't need them for the form
QMap<QString, QWidget*>::iterator it;
for (it = editWidgets.begin(); it != editWidgets.end(); ++it) {
KMyMoneyCombo* combo = dynamic_cast<KMyMoneyCombo*>(*it);
kMyMoneyLineEdit* edit = dynamic_cast<kMyMoneyLineEdit*>(*it);
KMyMoneyPayeeCombo* payee = dynamic_cast<KMyMoneyPayeeCombo*>(*it);
KTagContainer* tag = dynamic_cast<KTagContainer*>(*it);
if (combo)
combo->setPlaceholderText(QString());
if (edit)
edit->setPlaceholderText(QString());
if (payee)
payee->setPlaceholderText(QString());
if (tag)
tag->tagCombo()->setPlaceholderText(QString());
}
KMyMoneyTransactionForm::TransactionForm* form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(m_form);
TabBar* w = dynamic_cast<TabBar*>(editWidgets["tabbar"]);
if (w) {
// insert the tabbar in the boxlayout so it will take the place of the original tabbar which was hidden
QBoxLayout* boxLayout = dynamic_cast<QBoxLayout*>(form->tabBar()->parentWidget()->layout());
boxLayout->insertWidget(0, w);
}
}
void StdTransaction::tabOrderInForm(QWidgetList& tabOrderWidgets) const
{
QStringList taborder = KMyMoneyGlobalSettings::stdTransactionFormTabOrder().split(',', QString::SkipEmptyParts);
QStringList::const_iterator it_s = taborder.constBegin();
QWidget* w;
while (it_s != taborder.constEnd()) {
if (*it_s == "account") {
tabOrderWidgets.append(focusWidget(m_form->cellWidget(0, ValueColumn1)));
} else if (*it_s == "cashflow") {
tabOrderWidgets.append(focusWidget(m_form->cellWidget(1, LabelColumn1)));
} else if (*it_s == "payee") {
tabOrderWidgets.append(focusWidget(m_form->cellWidget(1, ValueColumn1)));
} else if (*it_s == "category") {
// make sure to have the category field and the split button as separate tab order widgets
// ok, we have to have some internal knowledge about the KMyMoneyCategory object, but
// it's one of our own widgets, so we actually don't care. Just make sure, that we don't
// go haywire when someone changes the KMyMoneyCategory object ...
QWidget* w = m_form->cellWidget(2, ValueColumn1);
tabOrderWidgets.append(focusWidget(w));
w = w->findChild<QPushButton*>("splitButton");
if (w)
tabOrderWidgets.append(w);
} else if (*it_s == "tag") {
tabOrderWidgets.append(focusWidget(m_form->cellWidget(3, ValueColumn1)));
} else if (*it_s == "memo") {
tabOrderWidgets.append(focusWidget(m_form->cellWidget(4, ValueColumn1)));
} else if (*it_s == "number") {
if (haveNumberField()) {
if ((w = focusWidget(m_form->cellWidget(1, ValueColumn2))))
tabOrderWidgets.append(w);
}
} else if (*it_s == "date") {
tabOrderWidgets.append(focusWidget(m_form->cellWidget(2, ValueColumn2)));
} else if (*it_s == "amount") {
tabOrderWidgets.append(focusWidget(m_form->cellWidget(3, ValueColumn2)));
} else if (*it_s == "state") {
tabOrderWidgets.append(focusWidget(m_form->cellWidget(5, ValueColumn2)));
}
++it_s;
}
}
void StdTransaction::arrangeWidgetsInRegister(QMap<QString, QWidget*>& editWidgets)
{
if (!m_parent)
return;
setupRegisterPalette(editWidgets);
if (haveNumberField())
arrangeWidget(m_parent, m_startRow + 0, NumberColumn, editWidgets["number"]);
arrangeWidget(m_parent, m_startRow + 0, DateColumn, editWidgets["postdate"]);
arrangeWidget(m_parent, m_startRow + 1, DateColumn, editWidgets["status"]);
arrangeWidget(m_parent, m_startRow + 0, DetailColumn, editWidgets["payee"]);
arrangeWidget(m_parent, m_startRow + 1, DetailColumn, editWidgets["category"]->parentWidget());
arrangeWidget(m_parent, m_startRow + 2, DetailColumn, editWidgets["tag"]);
arrangeWidget(m_parent, m_startRow + 3, DetailColumn, editWidgets["memo"]);
arrangeWidget(m_parent, m_startRow + 0, PaymentColumn, editWidgets["payment"]);
arrangeWidget(m_parent, m_startRow + 0, DepositColumn, editWidgets["deposit"]);
// increase the height of the row containing the memo widget
m_parent->setRowHeight(m_startRow + 3, m_parent->rowHeightHint() * 3);
}
void StdTransaction::tabOrderInRegister(QWidgetList& tabOrderWidgets) const
{
QStringList taborder = KMyMoneyGlobalSettings::stdTransactionRegisterTabOrder().split(',', QString::SkipEmptyParts);
QStringList::const_iterator it_s = taborder.constBegin();
QWidget* w;
while (it_s != taborder.constEnd()) {
if (*it_s == "number") {
if (haveNumberField()) {
if ((w = focusWidget(m_parent->cellWidget(m_startRow + 0, NumberColumn))))
tabOrderWidgets.append(w);
}
} else if (*it_s == "date") {
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 0, DateColumn)));
} else if (*it_s == "payee") {
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 0, DetailColumn)));
} else if (*it_s == "category") {
// make sure to have the category field and the split button as separate tab order widgets
// ok, we have to have some internal knowledge about the KMyMoneyCategory object, but
// it's one of our own widgets, so we actually don't care. Just make sure, that we don't
// go haywire when someone changes the KMyMoneyCategory object ...
w = m_parent->cellWidget(m_startRow + 1, DetailColumn);
tabOrderWidgets.append(focusWidget(w));
w = w->findChild<QPushButton*>("splitButton");
if (w)
tabOrderWidgets.append(w);
} else if (*it_s == "tag") {
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 2, DetailColumn)));
} else if (*it_s == "memo") {
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 3, DetailColumn)));
} else if (*it_s == "payment") {
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 0, PaymentColumn)));
} else if (*it_s == "deposit") {
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 0, DepositColumn)));
} else if (*it_s == "state") {
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 1, DateColumn)));
}
++it_s;
}
}
int StdTransaction::numRowsRegister(bool expanded) const
{
int numRows = 1;
if (expanded) {
numRows = 4;
if (!m_inEdit) {
//When not in edit Tags haven't a separate row;
numRows--;
if (m_payee.isEmpty()) {
numRows--;
}
if (m_split.memo().isEmpty()) {
numRows--;
}
// For income and expense accounts that only have
// two splits we only show one line, because the
// account name is already contained in the account column.
- if (m_account.accountType() == MyMoneyAccount::Income
- || m_account.accountType() == MyMoneyAccount::Expense) {
+ if (m_account.accountType() == eMyMoney::Account::Income
+ || m_account.accountType() == eMyMoney::Account::Expense) {
if (numRows > 2 && m_transaction.splitCount() == 2)
numRows = 1;
}
}
}
return numRows;
}
TransactionEditor* StdTransaction::createEditor(TransactionEditorContainer* regForm, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate)
{
#ifndef KMM_DESIGNER
m_inRegisterEdit = regForm == m_parent;
return new StdTransactionEditor(regForm, this, list, lastPostDate);
#else
return NULL;
#endif
}
InvestTransaction::InvestTransaction(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) :
Transaction(parent, transaction, split, uniqueId)
{
#ifndef KMM_DESIGNER
// dissect the transaction into its type, splits, currency, security etc.
KMyMoneyUtils::dissectTransaction(m_transaction, m_split,
m_assetAccountSplit,
m_feeSplits,
m_interestSplits,
m_security,
m_currency,
m_transactionType);
#endif
QList<MyMoneySplit>::ConstIterator it_s;
for (it_s = m_feeSplits.constBegin(); it_s != m_feeSplits.constEnd(); ++it_s) {
m_feeAmount += (*it_s).value();
}
for (it_s = m_interestSplits.constBegin(); it_s != m_interestSplits.constEnd(); ++it_s) {
m_interestAmount += (*it_s).value();
}
// check the count of the fee splits and setup the text
switch (m_feeSplits.count()) {
case 0:
break;
case 1:
m_feeCategory = MyMoneyFile::instance()->accountToCategory(m_feeSplits[0].accountId());
break;
default:
m_feeCategory = i18nc("Split transaction (category replacement)", "Split transaction");
break;
}
// check the count of the interest splits and setup the text
switch (m_interestSplits.count()) {
case 0:
break;
case 1:
m_interestCategory = MyMoneyFile::instance()->accountToCategory(m_interestSplits[0].accountId());
break;
default:
m_interestCategory = i18nc("Split transaction (category replacement)", "Split transaction");
break;
}
m_rowsForm = 7;
// setup initial size
setNumRowsRegister(numRowsRegister(KMyMoneyGlobalSettings::showRegisterDetailed()));
emit parent->itemAdded(this);
}
void InvestTransaction::setupForm(TransactionForm* form)
{
Transaction::setupForm(form);
form->setSpan(5, 1, 2, 1);
}
void InvestTransaction::activity(QString& txt, MyMoneySplit::investTransactionTypeE type) const
{
switch (type) {
case MyMoneySplit::AddShares:
txt = i18n("Add shares");
break;
case MyMoneySplit::RemoveShares:
txt = i18n("Remove shares");
break;
case MyMoneySplit::BuyShares:
txt = i18n("Buy shares");
break;
case MyMoneySplit::SellShares:
txt = i18n("Sell shares");
break;
case MyMoneySplit::Dividend:
txt = i18n("Dividend");
break;
case MyMoneySplit::ReinvestDividend:
txt = i18n("Reinvest Dividend");
break;
case MyMoneySplit::Yield:
txt = i18n("Yield");
break;
case MyMoneySplit::SplitShares:
txt = i18n("Split shares");
break;
case MyMoneySplit::InterestIncome:
txt = i18n("Interest Income");
break;
default:
txt = i18nc("Unknown investment activity", "Unknown");
break;
}
}
bool InvestTransaction::formCellText(QString& txt, Qt::Alignment& align, int row, int col, QPainter* /* painter */)
{
bool fieldEditable = false;
switch (row) {
case 0:
switch (col) {
case LabelColumn1:
align |= Qt::AlignLeft;
txt = i18n("Activity");
break;
case ValueColumn1:
align |= Qt::AlignLeft;
fieldEditable = true;
activity(txt, m_transactionType);
break;
case LabelColumn2:
align |= Qt::AlignLeft;
txt = i18n("Date");
break;
case ValueColumn2:
align |= Qt::AlignRight;
fieldEditable = true;
if (m_transaction != MyMoneyTransaction())
txt = QLocale().toString(m_transaction.postDate(), QLocale::ShortFormat);
break;
}
break;
case 1:
switch (col) {
case LabelColumn1:
align |= Qt::AlignLeft;
txt = i18n("Security");
break;
case ValueColumn1:
align |= Qt::AlignLeft;
fieldEditable = true;
if (m_account.isInvest())
txt = m_security.name();
break;
case LabelColumn2:
align |= Qt::AlignLeft;
if (haveShares()) {
txt = i18n("Shares");
} else if (haveSplitRatio()) {
txt = i18n("Ratio");
}
break;
case ValueColumn2:
align |= Qt::AlignRight;
if ((fieldEditable = haveShares()) == true) {
txt = m_split.shares().abs().formatMoney("", MyMoneyMoney::denomToPrec(m_security.smallestAccountFraction()));
} else if (haveSplitRatio()) {
txt = QString("1 / %1").arg(m_split.shares().abs().formatMoney("", -1));
}
break;
}
break;
case 2:
switch (col) {
case LabelColumn1:
align |= Qt::AlignLeft;
if (haveAssetAccount())
txt = i18n("Account");
break;
case ValueColumn1:
align |= Qt::AlignLeft;
if ((fieldEditable = haveAssetAccount()) == true) {
txt = MyMoneyFile::instance()->accountToCategory(m_assetAccountSplit.accountId());
}
break;
case LabelColumn2:
align |= Qt::AlignLeft;
if (havePrice())
txt = i18n("Price/share");
break;
case ValueColumn2:
align |= Qt::AlignRight;
if ((fieldEditable = havePrice()) == true && !m_split.shares().isZero()) {
txt = m_split.price().formatMoney("", m_security.pricePrecision());
}
break;
}
break;
case 3:
switch (col) {
case LabelColumn1:
align |= Qt::AlignLeft;
if (haveFees())
txt = i18n("Fees");
break;
case ValueColumn1:
align |= Qt::AlignLeft;
if ((fieldEditable = haveFees()) == true) {
txt = m_feeCategory;
}
break;
case LabelColumn2:
align |= Qt::AlignLeft;
if (haveFees() && !m_feeCategory.isEmpty())
txt = i18n("Fee Amount");
break;
case ValueColumn2:
align |= Qt::AlignRight;
if (haveFees()) {
if ((fieldEditable = !m_feeCategory.isEmpty()) == true) {
txt = MyMoneyUtils::formatMoney(m_feeAmount, m_currency);
}
}
break;
}
break;
case 4:
switch (col) {
case LabelColumn1:
align |= Qt::AlignLeft;
if (haveInterest())
txt = i18n("Interest");
break;
case ValueColumn1:
align |= Qt::AlignLeft;
if ((fieldEditable = haveInterest()) == true) {
txt = m_interestCategory;
}
break;
case LabelColumn2:
align |= Qt::AlignLeft;
if (haveInterest() && !m_interestCategory.isEmpty())
txt = i18n("Interest");
break;
case ValueColumn2:
align |= Qt::AlignRight;
if (haveInterest()) {
if ((fieldEditable = !m_interestCategory.isEmpty()) == true) {
txt = MyMoneyUtils::formatMoney(-m_interestAmount, m_currency);
}
}
break;
}
break;
case 5:
switch (col) {
case LabelColumn1:
align |= Qt::AlignLeft;
txt = i18n("Memo");
break;
case ValueColumn1:
align &= ~Qt::AlignVCenter;
align |= Qt::AlignTop;
align |= Qt::AlignLeft;
fieldEditable = true;
if (m_transaction != MyMoneyTransaction())
txt = m_split.memo().section('\n', 0, 2);
break;
case LabelColumn2:
align |= Qt::AlignLeft;
if (haveAmount())
txt = i18nc("Total balance", "Total");
break;
case ValueColumn2:
align |= Qt::AlignRight;
if ((fieldEditable = haveAmount()) == true) {
txt = m_assetAccountSplit.value().abs()
.formatMoney(m_currency.tradingSymbol(), MyMoneyMoney::denomToPrec(m_currency.smallestAccountFraction()));
}
}
break;
case 6:
switch (col) {
case LabelColumn2:
align |= Qt::AlignLeft;
txt = i18n("Status");
break;
case ValueColumn2:
align |= Qt::AlignRight;
fieldEditable = true;
txt = reconcileState();
break;
}
}
return fieldEditable;
}
void InvestTransaction::registerCellText(QString& txt, Qt::Alignment& align, int row, int col, QPainter* /* painter */)
{
switch (row) {
case 0:
switch (col) {
case DateColumn:
align |= Qt::AlignLeft;
txt = QLocale().toString(m_transaction.postDate(), QLocale::ShortFormat);
break;
case DetailColumn:
align |= Qt::AlignLeft;
activity(txt, m_transactionType);
break;
case SecurityColumn:
align |= Qt::AlignLeft;
if (m_account.isInvest())
txt = m_security.name();
break;
case ReconcileFlagColumn:
align |= Qt::AlignHCenter;
txt = reconcileState(false);
break;
case QuantityColumn:
align |= Qt::AlignRight;
if (haveShares())
txt = m_split.shares().abs().formatMoney("", MyMoneyMoney::denomToPrec(m_security.smallestAccountFraction()));
else if (haveSplitRatio()) {
txt = QString("1 / %1").arg(m_split.shares().abs().formatMoney("", -1));
}
break;
case PriceColumn:
align |= Qt::AlignRight;
if (havePrice() && !m_split.shares().isZero()) {
txt = m_split.price().formatMoney(m_currency.tradingSymbol(), m_security.pricePrecision());
}
break;
case ValueColumn:
align |= Qt::AlignRight;
if (haveAmount()) {
txt = MyMoneyUtils::formatMoney(m_assetAccountSplit.value().abs(), m_currency);
} else if (haveInterest()) {
txt = MyMoneyUtils::formatMoney(-m_interestAmount, m_currency);
}
break;
case BalanceColumn:
align |= Qt::AlignRight;
if (m_showBalance)
txt = m_balance.formatMoney("", MyMoneyMoney::denomToPrec(m_security.smallestAccountFraction()));
else
txt = "----";
break;
default:
break;
}
break;
case 1:
switch (col) {
case DetailColumn:
align |= Qt::AlignLeft;
if (haveAssetAccount() && !m_assetAccountSplit.accountId().isEmpty()) {
txt = MyMoneyFile::instance()->accountToCategory(m_assetAccountSplit.accountId());
} else if (haveInterest() && m_interestSplits.count()) {
txt = m_interestCategory;
} else if (haveFees() && m_feeSplits.count()) {
txt = m_feeCategory;
} else
singleLineMemo(txt, m_split);
break;
case QuantityColumn:
align |= Qt::AlignRight;
if (haveAssetAccount() && !m_assetAccountSplit.accountId().isEmpty()) {
// txt = m_interestAmount.abs().formatMoney(m_currency);
} else if (haveInterest() && m_interestSplits.count()) {
txt = MyMoneyUtils::formatMoney(-m_interestAmount, m_currency);
} else if (haveFees() && m_feeSplits.count()) {
txt = MyMoneyUtils::formatMoney(m_feeAmount, m_currency);
}
break;
default:
break;
}
break;
case 2:
switch (col) {
case DetailColumn:
align |= Qt::AlignLeft;
if (haveAssetAccount() && !m_assetAccountSplit.accountId().isEmpty()
&& haveInterest() && m_interestSplits.count()) {
txt = m_interestCategory;
} else if (haveFees() && m_feeSplits.count()) {
txt = m_feeCategory;
} else
singleLineMemo(txt, m_split);
break;
case QuantityColumn:
align |= Qt::AlignRight;
if (haveAssetAccount() && !m_assetAccountSplit.accountId().isEmpty()
&& haveInterest() && m_interestSplits.count()) {
txt = MyMoneyUtils::formatMoney(-m_interestAmount, m_currency);
} else if (haveFees() && m_feeSplits.count()) {
txt = MyMoneyUtils::formatMoney(m_feeAmount, m_currency);
}
break;
default:
break;
}
break;
case 3:
switch (col) {
case DetailColumn:
align |= Qt::AlignLeft;
if (haveAssetAccount() && !m_assetAccountSplit.accountId().isEmpty()
&& haveInterest() && m_interestSplits.count()
&& haveFees() && m_feeSplits.count()) {
txt = m_feeCategory;
} else
singleLineMemo(txt, m_split);
break;
case QuantityColumn:
align |= Qt::AlignRight;
if (haveAssetAccount() && !m_assetAccountSplit.accountId().isEmpty()
&& haveInterest() && m_interestSplits.count()
&& haveFees() && m_feeSplits.count()) {
txt = MyMoneyUtils::formatMoney(m_feeAmount, m_currency);
}
break;
default:
break;
}
break;
case 4:
switch (col) {
case DetailColumn:
align |= Qt::AlignLeft;
singleLineMemo(txt, m_split);
break;
default:
break;
}
break;
}
}
int InvestTransaction::registerColWidth(int col, const QFontMetrics& cellFontMetrics)
{
QString txt;
MyMoneyMoney amount;
int nw = 0;
// for now just check all rows in that column
for (int row = 0; row < m_rowsRegister; ++row) {
int w;
Transaction::registerCellText(txt, row, col);
w = cellFontMetrics.width(txt + " ");
nw = qMax(nw, w);
}
// TODO the optimized way would be to base the size on the contents of a single row
// as we do it in StdTransaction::registerColWidth()
#if 0
switch (col) {
default:
break;
case PriceColumn:
if (havePrice()) {
txt = (m_split.value() / m_split.shares()).formatMoney("", KMyMoneyGlobalSettings::pricePrecision());
nw = cellFontMetrics.width(txt + " ");
}
break;
}
#endif
return nw;
}
void InvestTransaction::arrangeWidgetsInForm(QMap<QString, QWidget*>& editWidgets)
{
if (!m_form || !m_parent)
return;
setupFormPalette(editWidgets);
// arrange the edit widgets
arrangeWidget(m_form, 0, ValueColumn1, editWidgets["activity"]);
arrangeWidget(m_form, 0, ValueColumn2, editWidgets["postdate"]);
arrangeWidget(m_form, 1, ValueColumn1, editWidgets["security"]);
arrangeWidget(m_form, 1, ValueColumn2, editWidgets["shares"]);
arrangeWidget(m_form, 2, ValueColumn1, editWidgets["asset-account"]);
arrangeWidget(m_form, 2, ValueColumn2, editWidgets["price"]);
arrangeWidget(m_form, 3, ValueColumn1, editWidgets["fee-account"]->parentWidget());
arrangeWidget(m_form, 3, ValueColumn2, editWidgets["fee-amount"]);
arrangeWidget(m_form, 4, ValueColumn1, editWidgets["interest-account"]->parentWidget());
arrangeWidget(m_form, 4, ValueColumn2, editWidgets["interest-amount"]);
arrangeWidget(m_form, 5, ValueColumn1, editWidgets["memo"]);
arrangeWidget(m_form, 5, ValueColumn2, editWidgets["total"]);
arrangeWidget(m_form, 6, ValueColumn2, editWidgets["status"]);
// arrange dynamic labels
arrangeWidget(m_form, 0, LabelColumn1, editWidgets["activity-label"]);
arrangeWidget(m_form, 0, LabelColumn2, editWidgets["postdate-label"]);
arrangeWidget(m_form, 1, LabelColumn1, editWidgets["security-label"]);
arrangeWidget(m_form, 1, LabelColumn2, editWidgets["shares-label"]);
arrangeWidget(m_form, 2, LabelColumn1, editWidgets["asset-label"]);
arrangeWidget(m_form, 2, LabelColumn2, editWidgets["price-label"]);
arrangeWidget(m_form, 3, LabelColumn1, editWidgets["fee-label"]);
arrangeWidget(m_form, 3, LabelColumn2, editWidgets["fee-amount-label"]);
arrangeWidget(m_form, 4, LabelColumn1, editWidgets["interest-label"]);
arrangeWidget(m_form, 4, LabelColumn2, editWidgets["interest-amount-label"]);
arrangeWidget(m_form, 5, LabelColumn1, editWidgets["memo-label"]);
arrangeWidget(m_form, 5, LabelColumn2, editWidgets["total-label"]);
arrangeWidget(m_form, 6, LabelColumn2, editWidgets["status-label"]);
// get rid of the hints. we don't need them for the form
QMap<QString, QWidget*>::iterator it;
for (it = editWidgets.begin(); it != editWidgets.end(); ++it) {
KMyMoneyCombo* combo = dynamic_cast<KMyMoneyCombo*>(*it);
kMyMoneyLineEdit* lineedit = dynamic_cast<kMyMoneyLineEdit*>(*it);
kMyMoneyEdit* edit = dynamic_cast<kMyMoneyEdit*>(*it);
KMyMoneyPayeeCombo* payee = dynamic_cast<KMyMoneyPayeeCombo*>(*it);
if (combo)
combo->setPlaceholderText(QString());
if (edit)
edit->setPlaceholderText(QString());
if (lineedit)
lineedit->setPlaceholderText(QString());
if (payee)
payee->setPlaceholderText(QString());
}
}
void InvestTransaction::tabOrderInForm(QWidgetList& tabOrderWidgets) const
{
// activity
tabOrderWidgets.append(focusWidget(m_form->cellWidget(0, ValueColumn1)));
// date
tabOrderWidgets.append(focusWidget(m_form->cellWidget(0, ValueColumn2)));
// security
tabOrderWidgets.append(focusWidget(m_form->cellWidget(1, ValueColumn1)));
// shares
tabOrderWidgets.append(focusWidget(m_form->cellWidget(1, ValueColumn2)));
// account
tabOrderWidgets.append(focusWidget(m_form->cellWidget(2, ValueColumn1)));
// price
tabOrderWidgets.append(focusWidget(m_form->cellWidget(2, ValueColumn2)));
// make sure to have the fee category field and the split button as separate tab order widgets
// ok, we have to have some internal knowledge about the KMyMoneyCategory object, but
// it's one of our own widgets, so we actually don't care. Just make sure, that we don't
// go haywire when someone changes the KMyMoneyCategory object ...
QWidget* w = m_form->cellWidget(3, ValueColumn1);
tabOrderWidgets.append(focusWidget(w));
w = w->findChild<QPushButton*>("splitButton");
if (w)
tabOrderWidgets.append(w);
// fee amount
tabOrderWidgets.append(focusWidget(m_form->cellWidget(3, ValueColumn2)));
// the same applies for the interest categories
w = m_form->cellWidget(4, ValueColumn1);
tabOrderWidgets.append(focusWidget(w));
w = w->findChild<QPushButton*>("splitButton");
if (w)
tabOrderWidgets.append(w);
// interest amount
tabOrderWidgets.append(focusWidget(m_form->cellWidget(4, ValueColumn2)));
// memo
tabOrderWidgets.append(focusWidget(m_form->cellWidget(5, ValueColumn1)));
// state
tabOrderWidgets.append(focusWidget(m_form->cellWidget(6, ValueColumn2)));
}
void InvestTransaction::arrangeWidgetsInRegister(QMap<QString, QWidget*>& editWidgets)
{
if (!m_parent)
return;
setupRegisterPalette(editWidgets);
arrangeWidget(m_parent, m_startRow + 0, DateColumn, editWidgets["postdate"]);
arrangeWidget(m_parent, m_startRow + 0, SecurityColumn, editWidgets["security"]);
arrangeWidget(m_parent, m_startRow + 0, DetailColumn, editWidgets["activity"]);
arrangeWidget(m_parent, m_startRow + 1, DetailColumn, editWidgets["asset-account"]);
arrangeWidget(m_parent, m_startRow + 2, DetailColumn, editWidgets["interest-account"]->parentWidget());
arrangeWidget(m_parent, m_startRow + 3, DetailColumn, editWidgets["fee-account"]->parentWidget());
arrangeWidget(m_parent, m_startRow + 4, DetailColumn, editWidgets["memo"]);
arrangeWidget(m_parent, m_startRow + 0, QuantityColumn, editWidgets["shares"]);
arrangeWidget(m_parent, m_startRow + 0, PriceColumn, editWidgets["price"]);
arrangeWidget(m_parent, m_startRow + 2, QuantityColumn, editWidgets["interest-amount"]);
arrangeWidget(m_parent, m_startRow + 3, QuantityColumn, editWidgets["fee-amount"]);
arrangeWidget(m_parent, m_startRow + 0, ValueColumn, editWidgets["total"]);
arrangeWidget(m_parent, m_startRow + 1, DateColumn, editWidgets["status"]);
// increase the height of the row containing the memo widget
m_parent->setRowHeight(m_startRow + 4, m_parent->rowHeightHint() * 3);
}
void InvestTransaction::tabOrderInRegister(QWidgetList& tabOrderWidgets) const
{
QWidget* w;
// date
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 0, DateColumn)));
// security
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 0, SecurityColumn)));
// activity
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 0, DetailColumn)));
// shares
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 0, QuantityColumn)));
// price
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 0, PriceColumn)));
// asset account
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 1, DetailColumn)));
// make sure to have the category fields and the split button as separate tab order widgets
// ok, we have to have some internal knowledge about the KMyMoneyCategory object, but
// it's one of our own widgets, so we actually don't care. Just make sure, that we don't
// go haywire when someone changes the KMyMoneyCategory object ...
w = m_parent->cellWidget(m_startRow + 2, DetailColumn); // interest account
tabOrderWidgets.append(focusWidget(w));
w = w->findChild<QPushButton*>("splitButton");
if (w)
tabOrderWidgets.append(w);
// interest amount
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 2, QuantityColumn)));
w = m_parent->cellWidget(m_startRow + 3, DetailColumn); // fee account
tabOrderWidgets.append(focusWidget(w));
w = w->findChild<QPushButton*>("splitButton");
if (w)
tabOrderWidgets.append(w);
// fee amount
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 3, QuantityColumn)));
// memo
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 4, DetailColumn)));
// status
tabOrderWidgets.append(focusWidget(m_parent->cellWidget(m_startRow + 1, DateColumn)));
}
int InvestTransaction::numRowsRegister(bool expanded) const
{
int numRows = 1;
if (expanded) {
if (!m_inEdit) {
if (haveAssetAccount() && !m_assetAccountSplit.accountId().isEmpty())
++numRows;
if (haveInterest() && m_interestSplits.count())
++numRows;
if (haveFees() && m_feeSplits.count())
++numRows;
if (!m_split.memo().isEmpty())
++numRows;
} else
numRows = 5;
}
return numRows;
}
bool InvestTransaction::haveShares() const
{
bool rc = true;
switch (m_transactionType) {
case MyMoneySplit::Dividend:
case MyMoneySplit::Yield:
case MyMoneySplit::SplitShares:
case MyMoneySplit::InterestIncome:
rc = false;
break;
default:
break;
}
return rc;
}
bool InvestTransaction::haveFees() const
{
bool rc = true;
switch (m_transactionType) {
case MyMoneySplit::AddShares:
case MyMoneySplit::RemoveShares:
case MyMoneySplit::SplitShares:
rc = false;
break;
default:
break;
}
return rc;
}
bool InvestTransaction::haveInterest() const
{
bool rc = false;
switch (m_transactionType) {
case MyMoneySplit::BuyShares:
case MyMoneySplit::SellShares:
case MyMoneySplit::Dividend:
case MyMoneySplit::ReinvestDividend:
case MyMoneySplit::Yield:
case MyMoneySplit::InterestIncome:
rc = true;
break;
default:
break;
}
return rc;
}
bool InvestTransaction::havePrice() const
{
bool rc = false;
switch (m_transactionType) {
case MyMoneySplit::BuyShares:
case MyMoneySplit::SellShares:
case MyMoneySplit::ReinvestDividend:
rc = true;
break;
default:
break;
}
return rc;
}
bool InvestTransaction::haveAmount() const
{
bool rc = false;
switch (m_transactionType) {
case MyMoneySplit::BuyShares:
case MyMoneySplit::SellShares:
case MyMoneySplit::Dividend:
case MyMoneySplit::Yield:
case MyMoneySplit::InterestIncome:
rc = true;
break;
default:
break;
}
return rc;
}
bool InvestTransaction::haveAssetAccount() const
{
bool rc = true;
switch (m_transactionType) {
case MyMoneySplit::AddShares:
case MyMoneySplit::RemoveShares:
case MyMoneySplit::SplitShares:
case MyMoneySplit::ReinvestDividend:
rc = false;
break;
default:
break;
}
return rc;
}
bool InvestTransaction::haveSplitRatio() const
{
return m_transactionType == MyMoneySplit::SplitShares;
}
void InvestTransaction::splits(MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& interestSplits, QList<MyMoneySplit>& feeSplits) const
{
assetAccountSplit = m_assetAccountSplit;
interestSplits = m_interestSplits;
feeSplits = m_feeSplits;
}
TransactionEditor* InvestTransaction::createEditor(TransactionEditorContainer* regForm, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate)
{
#ifndef KMM_DESIGNER
m_inRegisterEdit = regForm == m_parent;
return new InvestTransactionEditor(regForm, this, list, lastPostDate);
#else
return NULL;
#endif
}
diff --git a/kmymoney/widgets/transactionform.cpp b/kmymoney/widgets/transactionform.cpp
index ff66e4d37..b0a28b0a4 100644
--- a/kmymoney/widgets/transactionform.cpp
+++ b/kmymoney/widgets/transactionform.cpp
@@ -1,451 +1,451 @@
/***************************************************************************
transactionform.cpp
-------------------
begin : Sun May 14 2006
copyright : (C) 2006 by Thomas Baumgart
email : Thomas Baumgart <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 "transactionform.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QString>
#include <QPalette>
#include <QFrame>
#include <QHeaderView>
#include <QPushButton>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneydateinput.h"
#include "kmymoneyedit.h"
#include "kmymoneycategory.h"
#include "transaction.h"
#include "kmymoneyglobalsettings.h"
using namespace KMyMoneyTransactionForm;
TabBar::TabBar(QWidget* parent) :
QTabBar(parent),
m_signalType(SignalNormal)
{
connect(this, SIGNAL(currentChanged(int)), this, SLOT(slotTabCurrentChanged(int)));
}
TabBar::SignalEmissionE TabBar::setSignalEmission(TabBar::SignalEmissionE type)
{
TabBar::SignalEmissionE _type = m_signalType;
m_signalType = type;
return _type;
}
int TabBar::currentIndex() const
{
QMap<int, int>::const_iterator it;
int id = QTabBar::currentIndex();
for (it = m_idMap.constBegin(); it != m_idMap.constEnd(); ++it) {
if (*it == id) {
return it.key();
}
}
return -1;
}
void TabBar::setCurrentIndex(int id)
{
if (m_signalType != SignalNormal)
blockSignals(true);
if (m_idMap.contains(id)) {
QTabBar::setCurrentIndex(m_idMap[id]);
}
if (m_signalType != SignalNormal)
blockSignals(false);
if (m_signalType == SignalAlways)
emit currentChanged(m_idMap[id]);
}
void TabBar::setTabEnabled(int id, bool enable)
{
if (m_idMap.contains(id)) {
QTabBar::setTabEnabled(m_idMap[id], enable);
}
}
void TabBar::insertTab(int id, const QString& title)
{
int newId = QTabBar::insertTab(id, title);
m_idMap[id] = newId;
}
void TabBar::slotTabCurrentChanged(int id)
{
QMap<int, int>::const_iterator it;
for (it = m_idMap.constBegin(); it != m_idMap.constEnd(); ++it) {
if (*it == id) {
emit tabCurrentChanged(it.key());
break;
}
}
if (it == m_idMap.constEnd())
emit tabCurrentChanged(id);
}
void TabBar::showEvent(QShowEvent* event)
{
// make sure we don't emit a signal when simply showing the widget
if (m_signalType != SignalNormal)
blockSignals(true);
QTabBar::showEvent(event);
if (m_signalType != SignalNormal)
blockSignals(false);
}
void TabBar::copyTabs(const TabBar* otabbar)
{
// remove all existing tabs
while (count()) {
removeTab(0);
}
// now create new ones. copy text, icon and identifier
m_idMap = otabbar->m_idMap;
for (int i = 0; i < otabbar->count(); ++i) {
QTabBar::insertTab(i, otabbar->tabText(i));
if (i == otabbar->QTabBar::currentIndex()) {
QTabBar::setCurrentIndex(i);
}
}
}
int TabBar::indexAtPos(const QPoint& p) const
{
if (tabRect(QTabBar::currentIndex()).contains(p))
return QTabBar::currentIndex();
for (int i = 0; i < count(); ++i)
if (isTabEnabled(i) && tabRect(i).contains(p))
return i;
return -1;
}
void TabBar::mousePressEvent(QMouseEvent *e)
{
QTabBar::mousePressEvent(e);
// in case we receive a mouse press event on the current
// selected tab emit a signal no matter what as the base
// class does not do that
if (indexAtPos(e->pos()) == QTabBar::currentIndex()) {
slotTabCurrentChanged(QTabBar::currentIndex());
}
}
TransactionFormItemDelegate::TransactionFormItemDelegate(TransactionForm *parent) : QStyledItemDelegate(parent), m_transactionForm(parent)
{
}
TransactionFormItemDelegate::~TransactionFormItemDelegate()
{
}
void TransactionFormItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
m_transactionForm->paintCell(painter, option, index);
}
TransactionForm::TransactionForm(QWidget *parent) :
TransactionEditorContainer(parent),
m_transaction(0),
m_tabBar(0)
{
m_itemDelegate = new TransactionFormItemDelegate(this);
setFrameShape(QTableWidget::NoFrame);
setShowGrid(false);
setSelectionMode(QTableWidget::NoSelection);
verticalHeader()->hide();
horizontalHeader()->hide();
setEditTriggers(QAbstractItemView::NoEditTriggers);
// make sure, that the table is 'invisible' by setting up the right background
// keep the original color group for painting the cells though
QPalette p = palette();
QBrush brush = p.brush(QPalette::Background);
QColor color = brush.color();
color.setAlpha(0);
brush.setColor(color);
p.setBrush(QPalette::Active, QPalette::Base, brush);
p.setBrush(QPalette::Inactive, QPalette::Base, brush);
p.setBrush(QPalette::Disabled, QPalette::Base, brush);
setPalette(p);
slotSetTransaction(0);
}
bool TransactionForm::focusNextPrevChild(bool next)
{
return QFrame::focusNextPrevChild(next);
}
void TransactionForm::clear()
{
slotSetTransaction(0);
}
void TransactionForm::enableTabBar(bool b)
{
m_tabBar->setEnabled(b);
}
void TransactionForm::slotSetTransaction(KMyMoneyRegister::Transaction* transaction)
{
m_transaction = transaction;
setUpdatesEnabled(false);
if (m_transaction) {
// the next call sets up a back pointer to the form and also sets up the col and row span
// as well as the tab of the form
m_transaction->setupForm(this);
} else {
setRowCount(5);
setColumnCount(1);
}
kMyMoneyDateInput dateInput;
KMyMoneyCategory category(nullptr, true);
// extract the maximal sizeHint height
int height = qMax(dateInput.sizeHint().height(), category.sizeHint().height());
for (int row = 0; row < rowCount(); ++row) {
if (!transaction || transaction->showRowInForm(row)) {
showRow(row);
QTableWidget::setRowHeight(row, height);
} else
hideRow(row);
}
// adjust vertical size of form table
height *= rowCount();
setMaximumHeight(height);
setMinimumHeight(height);
setUpdatesEnabled(true); // see the call to setUpdatesEnabled(false) above
for (int i = 0; i < rowCount(); ++i) {
setItemDelegateForRow(i, m_itemDelegate);
}
// force resizeing of the columns
QMetaObject::invokeMethod(this, "resize", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(int, ValueColumn1));
}
void TransactionForm::paintCell(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index)
{
if (m_transaction) {
m_transaction->paintFormCell(painter, option, index);
}
}
TabBar* TransactionForm::tabBar(QWidget* parent)
{
if (!m_tabBar && parent) {
// determine the height of the objects in the table
// create the tab bar
m_tabBar = new TabBar(parent);
m_tabBar->setSignalEmission(TabBar::SignalAlways);
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
sizePolicy.setHeightForWidth(m_tabBar->sizePolicy().hasHeightForWidth());
m_tabBar->setSizePolicy(sizePolicy);
connect(m_tabBar, SIGNAL(tabCurrentChanged(int)), this, SLOT(slotActionSelected(int)));
}
return m_tabBar;
}
void TransactionForm::slotActionSelected(int id)
{
emit newTransaction(static_cast<KMyMoneyRegister::Action>(id));
}
void TransactionForm::setupForm(const MyMoneyAccount& acc)
{
bool blocked = m_tabBar->blockSignals(true);
// remove all tabs from the tabbar
while (m_tabBar->count())
m_tabBar->removeTab(0);
m_tabBar->show();
// important: one needs to add the new tabs first and then
// change the identifier. Otherwise, addTab() will assign
// a different value
switch (acc.accountType()) {
default:
m_tabBar->insertTab(KMyMoneyRegister::ActionDeposit, i18n("&Deposit"));
m_tabBar->insertTab(KMyMoneyRegister::ActionTransfer, i18n("&Transfer"));
m_tabBar->insertTab(KMyMoneyRegister::ActionWithdrawal, i18n("&Withdrawal"));
break;
- case MyMoneyAccount::CreditCard:
+ case eMyMoney::Account::CreditCard:
m_tabBar->insertTab(KMyMoneyRegister::ActionDeposit, i18n("&Payment"));
m_tabBar->insertTab(KMyMoneyRegister::ActionTransfer, i18n("&Transfer"));
m_tabBar->insertTab(KMyMoneyRegister::ActionWithdrawal, i18n("&Charge"));
break;
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Loan:
+ case eMyMoney::Account::Liability:
+ case eMyMoney::Account::Loan:
m_tabBar->insertTab(KMyMoneyRegister::ActionDeposit, i18n("&Decrease"));
m_tabBar->insertTab(KMyMoneyRegister::ActionTransfer, i18n("&Transfer"));
m_tabBar->insertTab(KMyMoneyRegister::ActionWithdrawal, i18n("&Increase"));
break;
- case MyMoneyAccount::Asset:
- case MyMoneyAccount::AssetLoan:
+ case eMyMoney::Account::Asset:
+ case eMyMoney::Account::AssetLoan:
m_tabBar->insertTab(KMyMoneyRegister::ActionDeposit, i18n("&Increase"));
m_tabBar->insertTab(KMyMoneyRegister::ActionTransfer, i18n("&Transfer"));
m_tabBar->insertTab(KMyMoneyRegister::ActionWithdrawal, i18n("&Decrease"));
break;
- case MyMoneyAccount::Income:
- case MyMoneyAccount::Expense:
- case MyMoneyAccount::Investment:
- case MyMoneyAccount::Stock:
+ case eMyMoney::Account::Income:
+ case eMyMoney::Account::Expense:
+ case eMyMoney::Account::Investment:
+ case eMyMoney::Account::Stock:
m_tabBar->hide();
break;
}
m_tabBar->blockSignals(blocked);
}
void TransactionForm::resize(int col)
{
setUpdatesEnabled(false);
// resize the register
int w = viewport()->width();
int nc = columnCount();
// check which space we need
if (nc >= LabelColumn1 && columnWidth(LabelColumn1))
adjustColumn(LabelColumn1);
if (nc >= ValueColumn1 && columnWidth(ValueColumn1))
adjustColumn(ValueColumn1);
if (nc >= LabelColumn2 && columnWidth(LabelColumn2))
adjustColumn(LabelColumn2);
if (nc >= ValueColumn2 && columnWidth(ValueColumn2))
adjustColumn(ValueColumn2);
for (int i = 0; i < nc; ++i) {
if (i == col)
continue;
w -= columnWidth(i);
}
if (col < nc && w >= 0)
setColumnWidth(col, w);
setUpdatesEnabled(true);
}
void TransactionForm::adjustColumn(Column col)
{
int w = 0;
// preset the width of the right value column with the width of
// the possible edit widgets so that they fit if they pop up
if (col == ValueColumn2) {
kMyMoneyDateInput dateInput;
kMyMoneyEdit valInput;
w = qMax(dateInput.sizeHint().width(), valInput.sizeHint().width());
}
if (m_transaction) {
QString txt;
QFontMetrics fontMetrics(KMyMoneyGlobalSettings::listCellFont());
// scan through the rows
for (int i = rowCount() - 1; i >= 0; --i) {
Qt::Alignment align;
int spacing = 10;
m_transaction->formCellText(txt, align, i, static_cast<int>(col), 0);
QWidget* cw = cellWidget(i, col);
if (cw) {
w = qMax(w, cw->sizeHint().width() + spacing);
// if the cell widget contains a push button increase the spacing used
// for the cell text value to consider the size of the push button
if (QPushButton *pushButton = cw->findChild<QPushButton *>()) {
spacing += pushButton->sizeHint().width() + 5;
}
}
w = qMax(w, fontMetrics.width(txt) + spacing);
}
}
if (col < columnCount())
setColumnWidth(col, w);
}
void TransactionForm::arrangeEditWidgets(QMap<QString, QWidget*>& editWidgets, KMyMoneyRegister::Transaction* t)
{
t->arrangeWidgetsInForm(editWidgets);
resize(ValueColumn1);
}
void TransactionForm::tabOrder(QWidgetList& tabOrderWidgets, KMyMoneyRegister::Transaction* t) const
{
t->tabOrderInForm(tabOrderWidgets);
}
void TransactionForm::removeEditWidgets(QMap<QString, QWidget*>& editWidgets)
{
QMap<QString, QWidget*>::iterator it;
for (it = editWidgets.begin(); it != editWidgets.end();) {
if ((*it)->parentWidget() == this) {
editWidgets.erase(it);
it = editWidgets.begin();
} else
++it;
}
for (int row = 0; row < rowCount(); ++row) {
for (int col = 0; col < columnCount(); ++col) {
if (cellWidget(row, col)) {
cellWidget(row, col)->hide();
setCellWidget(row, col, 0);
}
}
}
resize(ValueColumn1);
// delete all remaining edit widgets (e.g. tabbar)
for (it = editWidgets.begin(); it != editWidgets.end();) {
delete(*it); // ->deleteLater();
editWidgets.erase(it);
it = editWidgets.begin();
}
}
diff --git a/kmymoney/wizards/endingbalancedlg/kendingbalancedlg.cpp b/kmymoney/wizards/endingbalancedlg/kendingbalancedlg.cpp
index 1f44f2e30..88584f99a 100644
--- a/kmymoney/wizards/endingbalancedlg/kendingbalancedlg.cpp
+++ b/kmymoney/wizards/endingbalancedlg/kendingbalancedlg.cpp
@@ -1,383 +1,384 @@
/***************************************************************************
kendingbalancedlg.cpp
-------------------
copyright : (C) 2000,2003 by Michael Edwardes, Thomas Baumgart
email : mte@users.sourceforge.net
ipwizard@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "kendingbalancedlg.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
#include <QBitArray>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KStandardGuiItem>
#include <KHelpClient>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyutils.h"
#include "kmymoneyedit.h"
#include "mymoneysplit.h"
#include "mymoneyfile.h"
+#include "mymoneyinstitution.h"
#include "kmymoneycategory.h"
#include "kmymoneyaccountselector.h"
#include "kmymoneyutils.h"
#include "kcurrencycalculator.h"
#include "kmymoneysettings.h"
class KEndingBalanceDlg::Private
{
public:
explicit Private(int numPages)
: m_pages(numPages, true) {}
MyMoneyTransaction m_tInterest;
MyMoneyTransaction m_tCharges;
MyMoneyAccount m_account;
QMap<QWidget*, QString> m_helpAnchor;
QBitArray m_pages;
};
KEndingBalanceDlg::KEndingBalanceDlg(const MyMoneyAccount& account, QWidget *parent) :
KEndingBalanceDlgDecl(parent),
d(new Private(Page_InterestChargeCheckings + 1))
{
setModal(true);
QString value;
MyMoneyMoney endBalance, startBalance;
d->m_account = account;
MyMoneySecurity currency = MyMoneyFile::instance()->security(account.currencyId());
//FIXME: port
m_statementInfoPageCheckings->m_enterInformationLabel->setText(QString("<qt>") + i18n("Please enter the following fields with the information as you find them on your statement. Make sure to enter all values in <b>%1</b>.", currency.name()) + QString("</qt>"));
// If the previous reconciliation was postponed,
// we show a different first page
value = account.value("lastReconciledBalance");
if (value.isEmpty()) {
// if the last statement has been entered long enough ago (more than one month),
// then take the last statement date and add one month and use that as statement
// date.
QDate lastStatementDate = account.lastReconciliationDate();
if (lastStatementDate.addMonths(1) < QDate::currentDate()) {
setField("statementDate", lastStatementDate.addMonths(1));
}
slotUpdateBalances();
d->m_pages.clearBit(Page_PreviousPostpone);
} else {
d->m_pages.clearBit(Page_CheckingStart);
d->m_pages.clearBit(Page_InterestChargeCheckings);
//removePage(m_interestChargeCheckings);
// make sure, we show the correct start page
setStartId(Page_PreviousPostpone);
MyMoneyMoney factor(1, 1);
- if (d->m_account.accountGroup() == MyMoneyAccount::Liability)
+ if (d->m_account.accountGroup() == eMyMoney::Account::Liability)
factor = -factor;
startBalance = MyMoneyMoney(value) * factor;
value = account.value("statementBalance");
endBalance = MyMoneyMoney(value) * factor;
//FIXME: port
m_statementInfoPageCheckings->m_previousBalance->setValue(startBalance);
m_statementInfoPageCheckings->m_endingBalance->setValue(endBalance);
}
// We don't need to add the default into the list (see ::help() why)
// m_helpAnchor[m_startPageCheckings] = QString("");
d->m_helpAnchor[m_interestChargeCheckings] = QString("details.reconcile.wizard.interest");
d->m_helpAnchor[m_statementInfoPageCheckings] = QString("details.reconcile.wizard.statement");
value = account.value("statementDate");
if (!value.isEmpty())
setField("statementDate", QDate::fromString(value, Qt::ISODate));
//FIXME: port
m_statementInfoPageCheckings->m_lastStatementDate->setText(QString());
if (account.lastReconciliationDate().isValid()) {
m_statementInfoPageCheckings->m_lastStatementDate->setText(i18n("Last reconciled statement: %1", QLocale().toString(account.lastReconciliationDate())));
}
// connect the signals with the slots
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotReloadEditWidgets()));
connect(m_statementInfoPageCheckings->m_statementDate, SIGNAL(dateChanged(QDate)), this, SLOT(slotUpdateBalances()));
connect(m_interestChargeCheckings->m_interestCategoryEdit, SIGNAL(createItem(QString,QString&)), this, SLOT(slotCreateInterestCategory(QString,QString&)));
connect(m_interestChargeCheckings->m_chargesCategoryEdit, SIGNAL(createItem(QString,QString&)), this, SLOT(slotCreateChargesCategory(QString,QString&)));
connect(m_interestChargeCheckings->m_payeeEdit, SIGNAL(createItem(QString,QString&)), this, SIGNAL(createPayee(QString,QString&)));
KMyMoneyMVCCombo::setSubstringSearchForChildren(m_interestChargeCheckings, !KMyMoneySettings::stringMatchFromStart());
slotReloadEditWidgets();
// preset payee if possible
try {
// if we find a payee with the same name as the institution,
// than this is what we use as payee.
if (!d->m_account.institutionId().isEmpty()) {
MyMoneyInstitution inst = MyMoneyFile::instance()->institution(d->m_account.institutionId());
MyMoneyPayee payee = MyMoneyFile::instance()->payeeByName(inst.name());
setField("payeeEdit", payee.id());
}
} catch (const MyMoneyException &) {
}
KMyMoneyUtils::updateWizardButtons(this);
// setup different text and icon on finish button
setButtonText(QWizard::FinishButton, KStandardGuiItem::cont().text());
button(QWizard::FinishButton)->setIcon(KStandardGuiItem::cont().icon());
}
KEndingBalanceDlg::~KEndingBalanceDlg()
{
delete d;
}
void KEndingBalanceDlg::slotUpdateBalances()
{
MYMONEYTRACER(tracer);
// determine the beginning balance and ending balance based on the following
// forumulas:
//
// end balance = current balance - sum(all non cleared transactions)
// - sum(all cleared transactions posted
// after statement date)
// start balance = end balance - sum(all cleared transactions
// up to statement date)
MyMoneyTransactionFilter filter(d->m_account.id());
- filter.addState(MyMoneyTransactionFilter::notReconciled);
+ filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled);
filter.setReportAllSplits(true);
QList<QPair<MyMoneyTransaction, MyMoneySplit> > transactionList;
QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
// retrieve the list from the engine
MyMoneyFile::instance()->transactionList(transactionList, filter);
//first retrieve the oldest not reconciled transaction
QDate oldestTransactionDate;
it = transactionList.constBegin();
if (it != transactionList.constEnd()) {
oldestTransactionDate = (*it).first.postDate();
m_statementInfoPageCheckings->m_oldestTransactionDate->setText(i18n("Oldest unmarked transaction: %1", QLocale().toString(oldestTransactionDate)));
}
- filter.addState(MyMoneyTransactionFilter::cleared);
+ filter.addState((int)eMyMoney::TransactionFilter::State::Cleared);
// retrieve the list from the engine to calculate the starting and ending balance
MyMoneyFile::instance()->transactionList(transactionList, filter);
MyMoneyMoney balance = MyMoneyFile::instance()->balance(d->m_account.id());
MyMoneyMoney factor(1, 1);
- if (d->m_account.accountGroup() == MyMoneyAccount::Liability)
+ if (d->m_account.accountGroup() == eMyMoney::Account::Liability)
factor = -factor;
MyMoneyMoney endBalance, startBalance;
balance = balance * factor;
endBalance = startBalance = balance;
tracer.printf("total balance = %s", qPrintable(endBalance.formatMoney("", 2)));
for (it = transactionList.constBegin(); it != transactionList.constEnd(); ++it) {
const MyMoneySplit& split = (*it).second;
balance -= split.shares() * factor;
if ((*it).first.postDate() > field("statementDate").toDate()) {
tracer.printf("Reducing balances by %s because postdate of %s/%s(%s) is past statement date", qPrintable((split.shares() * factor).formatMoney("", 2)), qPrintable((*it).first.id()), qPrintable(split.id()), qPrintable((*it).first.postDate().toString(Qt::ISODate)));
endBalance -= split.shares() * factor;
startBalance -= split.shares() * factor;
} else {
switch (split.reconcileFlag()) {
case MyMoneySplit::NotReconciled:
tracer.printf("Reducing balances by %s because %s/%s(%s) is not reconciled", qPrintable((split.shares() * factor).formatMoney("", 2)), qPrintable((*it).first.id()), qPrintable(split.id()), qPrintable((*it).first.postDate().toString(Qt::ISODate)));
endBalance -= split.shares() * factor;
startBalance -= split.shares() * factor;
break;
case MyMoneySplit::Cleared:
tracer.printf("Reducing start balance by %s because %s/%s(%s) is cleared", qPrintable((split.shares() * factor).formatMoney("", 2)), qPrintable((*it).first.id()), qPrintable(split.id()), qPrintable((*it).first.postDate().toString(Qt::ISODate)));
startBalance -= split.shares() * factor;
break;
default:
break;
}
}
}
//FIXME: port
m_statementInfoPageCheckings->m_previousBalance->setValue(startBalance);
m_statementInfoPageCheckings->m_endingBalance->setValue(endBalance);
tracer.printf("total balance = %s", qPrintable(endBalance.formatMoney("", 2)));
tracer.printf("start balance = %s", qPrintable(startBalance.formatMoney("", 2)));
setField("interestDateEdit", field("statementDate").toDate());
setField("chargesDateEdit", field("statementDate").toDate());
}
void KEndingBalanceDlg::accept()
{
if ((!field("interestEditValid").toBool() || createTransaction(d->m_tInterest, -1, field("interestEdit").value<MyMoneyMoney>(), field("interestCategoryEdit").toString(), field("interestDateEdit").toDate()))
&& (!field("chargesEditValid").toBool() || createTransaction(d->m_tCharges, 1, field("chargesEdit").value<MyMoneyMoney>(), field("chargesCategoryEdit").toString(), field("chargesDateEdit").toDate())))
KEndingBalanceDlgDecl::accept();
}
void KEndingBalanceDlg::slotCreateInterestCategory(const QString& txt, QString& id)
{
createCategory(txt, id, MyMoneyFile::instance()->income());
}
void KEndingBalanceDlg::slotCreateChargesCategory(const QString& txt, QString& id)
{
createCategory(txt, id, MyMoneyFile::instance()->expense());
}
void KEndingBalanceDlg::createCategory(const QString& txt, QString& id, const MyMoneyAccount& parent)
{
MyMoneyAccount acc;
acc.setName(txt);
emit createCategory(acc, parent);
id = acc.id();
}
const MyMoneyMoney KEndingBalanceDlg::endingBalance() const
{
return adjustedReturnValue(m_statementInfoPageCheckings->m_endingBalance->value());
}
const MyMoneyMoney KEndingBalanceDlg::previousBalance() const
{
return adjustedReturnValue(m_statementInfoPageCheckings->m_previousBalance->value());
}
const MyMoneyMoney KEndingBalanceDlg::adjustedReturnValue(const MyMoneyMoney& v) const
{
- return d->m_account.accountGroup() == MyMoneyAccount::Liability ? -v : v;
+ return d->m_account.accountGroup() == eMyMoney::Account::Liability ? -v : v;
}
void KEndingBalanceDlg::slotReloadEditWidgets()
{
QString payeeId, interestId, chargesId;
// keep current selected items
payeeId = field("payeeEdit").toString();
interestId = field("interestCategoryEdit").toString();
chargesId = field("chargesCategoryEdit").toString();
// load the payee and category widgets with data from the engine
//FIXME: port
m_interestChargeCheckings->m_payeeEdit->loadPayees(MyMoneyFile::instance()->payeeList());
// a user request to show all categories in both selectors due to a valid use case.
AccountSet aSet;
- aSet.addAccountGroup(MyMoneyAccount::Expense);
- aSet.addAccountGroup(MyMoneyAccount::Income);
+ aSet.addAccountGroup(eMyMoney::Account::Expense);
+ aSet.addAccountGroup(eMyMoney::Account::Income);
//FIXME: port
aSet.load(m_interestChargeCheckings->m_interestCategoryEdit->selector());
aSet.load(m_interestChargeCheckings->m_chargesCategoryEdit->selector());
// reselect currently selected items
if (!payeeId.isEmpty())
setField("payeeEdit", payeeId);
if (!interestId.isEmpty())
setField("interestCategoryEdit", interestId);
if (!chargesId.isEmpty())
setField("chargesCategoryEdit", chargesId);
}
const MyMoneyTransaction KEndingBalanceDlg::interestTransaction()
{
return d->m_tInterest;
}
const MyMoneyTransaction KEndingBalanceDlg::chargeTransaction()
{
return d->m_tCharges;
}
bool KEndingBalanceDlg::createTransaction(MyMoneyTransaction &t, const int sign, const MyMoneyMoney& amount, const QString& category, const QDate& date)
{
t = MyMoneyTransaction();
if (category.isEmpty() || !date.isValid())
return true;
MyMoneySplit s1, s2;
MyMoneyMoney val = amount * MyMoneyMoney(sign, 1);
try {
t.setPostDate(date);
t.setCommodity(d->m_account.currencyId());
s1.setPayeeId(field("payeeEdit").toString());
s1.setReconcileFlag(MyMoneySplit::Cleared);
s1.setAccountId(d->m_account.id());
s1.setValue(-val);
s1.setShares(-val);
s2 = s1;
s2.setAccountId(category);
s2.setValue(val);
t.addSplit(s1);
t.addSplit(s2);
QMap<QString, MyMoneyMoney> priceInfo; // just empty
MyMoneyMoney shares;
if (!KCurrencyCalculator::setupSplitPrice(shares, t, s2, priceInfo, this)) {
t = MyMoneyTransaction();
return false;
}
s2.setShares(shares);
t.modifySplit(s2);
} catch (const MyMoneyException &e) {
qDebug("%s", qPrintable(e.what()));
t = MyMoneyTransaction();
return false;
}
return true;
}
void KEndingBalanceDlg::help()
{
QString anchor = d->m_helpAnchor[currentPage()];
if (anchor.isEmpty())
anchor = QString("details.reconcile.whatis");
KHelpClient::invokeHelp(anchor);
}
int KEndingBalanceDlg::nextId() const
{
// Starting from the current page, look for the first enabled page
// and return that value
// If the end of the list is encountered first, then return -1.
for (int i = currentId() + 1; i < d->m_pages.size() && i < pageIds().size(); ++i) {
if (d->m_pages.testBit(i))
return pageIds()[i];
}
return -1;
}
diff --git a/kmymoney/wizards/newaccountwizard/knewaccountwizard.cpp b/kmymoney/wizards/newaccountwizard/knewaccountwizard.cpp
index 65f51aac3..626801a6b 100644
--- a/kmymoney/wizards/newaccountwizard/knewaccountwizard.cpp
+++ b/kmymoney/wizards/newaccountwizard/knewaccountwizard.cpp
@@ -1,1667 +1,1668 @@
/***************************************************************************
knewaccountwizard.cpp
-------------------
begin : Tue Sep 25 2006
copyright : (C) 2007 Thomas Baumgart
email : Thomas Baumgart <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 "knewaccountwizard.h"
#include "knewaccountwizard_p.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QCheckBox>
#include <QLabel>
#include <QList>
#include <qmath.h>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KLineEdit>
#include <KMessageBox>
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneycurrencyselector.h"
#include "kmymoneyaccountselector.h"
#include "mymoneyfinancialcalculator.h"
#include "kcurrencycalculator.h"
#include "kmymoneyglobalsettings.h"
#include <kguiutils.h>
#include "mymoneyutils.h"
#include "ksplittransactiondlg.h"
#include "kequitypriceupdatedlg.h"
#include "accountsmodel.h"
#include "accountsproxymodel.h"
#include "models.h"
#include "modelenums.h"
#include "icons.h"
#include "mymoneyfile.h"
using namespace NewAccountWizard;
using namespace Icons;
+using namespace eMyMoney;
namespace NewAccountWizard
{
enum steps {
StepInstitution = 1,
StepAccount,
StepBroker,
StepDetails,
StepPayments,
StepFees,
StepSchedule,
StepPayout,
StepParentAccount,
StepFinish
};
Wizard::Wizard(QWidget *parent, bool modal, Qt::WindowFlags flags)
: KMyMoneyWizard(parent, modal, flags)
{
setTitle(i18n("KMyMoney New Account Setup"));
addStep(i18n("Institution"));
addStep(i18n("Account"));
addStep(i18n("Broker"));
addStep(i18n("Details"));
addStep(i18n("Payments"));
addStep(i18n("Fees"));
addStep(i18n("Schedule"));
addStep(i18n("Payout"));
addStep(i18n("Parent Account"));
addStep(i18nc("Finish the wizard", "Finish"));
setStepHidden(StepBroker);
setStepHidden(StepSchedule);
setStepHidden(StepPayout);
setStepHidden(StepDetails);
setStepHidden(StepPayments);
setStepHidden(StepFees);
m_institutionPage = new InstitutionPage(this);
m_accountTypePage = new AccountTypePage(this);
// Investment Pages
m_brokeragepage = new BrokeragePage(this);
// Credit Card Pages
m_schedulePage = new CreditCardSchedulePage(this);
// Loan Pages
m_generalLoanInfoPage = new GeneralLoanInfoPage(this);
m_loanDetailsPage = new LoanDetailsPage(this);
m_loanPaymentPage = new LoanPaymentPage(this);
m_loanSchedulePage = new LoanSchedulePage(this);
m_loanPayoutPage = new LoanPayoutPage(this);
// Not a loan page
m_hierarchyPage = new HierarchyPage(this);
// Finish
m_accountSummaryPage = new AccountSummaryPage(this);
setFirstPage(m_institutionPage);
}
void Wizard::setAccount(const MyMoneyAccount& acc)
{
m_account = acc;
m_accountTypePage->setAccount(m_account);
if (!acc.institutionId().isEmpty()) {
m_institutionPage->selectExistingInstitution(acc.institutionId());
}
}
const MyMoneySecurity& Wizard::currency() const
{
return m_accountTypePage->currency();
}
MyMoneyMoney Wizard::interestRate() const
{
return m_loanDetailsPage->m_interestRate->value() / MyMoneyMoney(100, 1);
}
int Wizard::precision() const
{
return MyMoneyMoney::denomToPrec(currency().smallestAccountFraction());
}
const MyMoneyAccount& Wizard::account()
{
m_account = MyMoneyAccountLoan();
m_account.setName(m_accountTypePage->m_accountName->text());
m_account.setOpeningDate(m_accountTypePage->m_openingDate->date());
m_account.setAccountType(m_accountTypePage->accountType());
m_account.setInstitutionId(m_institutionPage->institution().id());
m_account.setNumber(m_institutionPage->m_accountNumber->text());
m_account.setValue("iban", m_institutionPage->m_iban->text());
if (m_accountTypePage->m_preferredAccount->isChecked())
m_account.setValue("PreferredAccount", "Yes");
else
m_account.deletePair("PreferredAccount");
m_account.setCurrencyId(currency().id());
if (m_account.isLoan()) {
// in case we lend the money we adjust the account type
if (!moneyBorrowed())
- m_account.setAccountType(MyMoneyAccount::AssetLoan);
+ m_account.setAccountType(Account::AssetLoan);
m_account.setLoanAmount(m_loanDetailsPage->m_loanAmount->value());
m_account.setInterestRate(m_loanSchedulePage->firstPaymentDueDate(), m_loanDetailsPage->m_interestRate->value());
m_account.setInterestCalculation(m_loanDetailsPage->m_paymentDue->currentIndex() == 0 ? MyMoneyAccountLoan::paymentReceived : MyMoneyAccountLoan::paymentDue);
m_account.setFixedInterestRate(m_generalLoanInfoPage->m_interestType->currentIndex() == 0);
m_account.setFinalPayment(m_loanDetailsPage->m_balloonAmount->value());
m_account.setTerm(m_loanDetailsPage->term());
m_account.setPeriodicPayment(m_loanDetailsPage->m_paymentAmount->value());
m_account.setPayee(m_generalLoanInfoPage->m_payee->selectedItem());
- m_account.setInterestCompounding(m_generalLoanInfoPage->m_compoundFrequency->currentItem());
+ m_account.setInterestCompounding((int)m_generalLoanInfoPage->m_compoundFrequency->currentItem());
if (!m_account.fixedInterestRate()) {
m_account.setNextInterestChange(m_generalLoanInfoPage->m_interestChangeDateEdit->date());
m_account.setInterestChangeFrequency(m_generalLoanInfoPage->m_interestFrequencyAmountEdit->value(), m_generalLoanInfoPage->m_interestFrequencyUnitEdit->currentIndex());
}
}
return m_account;
}
MyMoneyTransaction Wizard::payoutTransaction()
{
MyMoneyTransaction t;
if (m_account.isLoan() // we're creating a loan
&& openingBalance().isZero() // and don't have an opening balance
&& !m_loanPayoutPage->m_noPayoutTransaction->isChecked()) { // and the user wants to have a payout transaction
t.setPostDate(m_loanPayoutPage->m_payoutDate->date());
t.setCommodity(m_account.currencyId());
MyMoneySplit s;
s.setAccountId(m_account.id());
s.setShares(m_loanDetailsPage->m_loanAmount->value());
if (moneyBorrowed())
s.setShares(-s.shares());
s.setValue(s.shares());
t.addSplit(s);
s.clearId();
s.setValue(-s.value());
s.setAccountId(m_loanPayoutPage->payoutAccountId());
MyMoneyMoney shares;
KCurrencyCalculator::setupSplitPrice(shares, t, s, QMap<QString, MyMoneyMoney>(), this);
s.setShares(shares);
t.addSplit(s);
}
return t;
}
const MyMoneyAccount& Wizard::parentAccount()
{
return m_accountTypePage->allowsParentAccount()
? m_hierarchyPage->parentAccount()
- : (m_accountTypePage->accountType() == MyMoneyAccount::Loan
+ : (m_accountTypePage->accountType() == Account::Loan
? m_generalLoanInfoPage->parentAccount()
: m_accountTypePage->parentAccount());
}
MyMoneyAccount Wizard::brokerageAccount() const
{
MyMoneyAccount account;
- if (m_account.accountType() == MyMoneyAccount::Investment
+ if (m_account.accountType() == Account::Investment
&& m_brokeragepage->m_createBrokerageButton->isChecked()) {
account.setName(m_account.brokerageName());
- account.setAccountType(MyMoneyAccount::Checkings);
+ account.setAccountType(Account::Checkings);
account.setInstitutionId(m_account.institutionId());
account.setOpeningDate(m_account.openingDate());
account.setCurrencyId(m_brokeragepage->m_brokerageCurrency->security().id());
if (m_brokeragepage->m_accountNumber->isEnabled() && !m_brokeragepage->m_accountNumber->text().isEmpty())
account.setNumber(m_brokeragepage->m_accountNumber->text());
if (m_brokeragepage->m_iban->isEnabled() && !m_brokeragepage->m_iban->text().isEmpty())
account.setValue("iban", m_brokeragepage->m_iban->text());
}
return account;
}
const MyMoneySchedule& Wizard::schedule()
{
m_schedule = MyMoneySchedule();
if (!m_account.id().isEmpty()) {
- if (m_schedulePage->m_reminderCheckBox->isChecked() && (m_account.accountType() == MyMoneyAccount::CreditCard)) {
+ if (m_schedulePage->m_reminderCheckBox->isChecked() && (m_account.accountType() == Account::CreditCard)) {
m_schedule.setName(m_schedulePage->m_name->text());
- m_schedule.setType(MyMoneySchedule::TYPE_TRANSFER);
- m_schedule.setPaymentType(static_cast<MyMoneySchedule::paymentTypeE>(m_schedulePage->m_method->currentItem()));
+ m_schedule.setType(Schedule::Type::Transfer);
+ m_schedule.setPaymentType(static_cast<Schedule::PaymentType>(m_schedulePage->m_method->currentItem()));
m_schedule.setFixed(false);
- m_schedule.setOccurrencePeriod(MyMoneySchedule::OCCUR_MONTHLY);
+ m_schedule.setOccurrencePeriod(Schedule::Occurrence::Monthly);
m_schedule.setOccurrenceMultiplier(1);
MyMoneyTransaction t;
MyMoneySplit s;
s.setPayeeId(m_schedulePage->m_payee->selectedItem());
s.setAccountId(m_schedulePage->m_paymentAccount->selectedItem());
s.setMemo(i18n("Credit card payment"));
s.setShares(-m_schedulePage->m_amount->value());
s.setValue(s.shares());
t.addSplit(s);
s.clearId();
s.setAccountId(m_account.id());
s.setShares(m_schedulePage->m_amount->value());
s.setValue(s.shares());
t.addSplit(s);
// setup the next due date
t.setPostDate(m_schedulePage->m_date->date());
m_schedule.setTransaction(t);
} else if (m_account.isLoan()) {
m_schedule.setName(i18n("Loan payment for %1", m_accountTypePage->m_accountName->text()));
- m_schedule.setType(MyMoneySchedule::TYPE_LOANPAYMENT);
+ m_schedule.setType(Schedule::Type::LoanPayment);
if (moneyBorrowed())
- m_schedule.setPaymentType(MyMoneySchedule::STYPE_DIRECTDEBIT);
+ m_schedule.setPaymentType(Schedule::PaymentType::DirectDebit);
else
- m_schedule.setPaymentType(MyMoneySchedule::STYPE_DIRECTDEPOSIT);
+ m_schedule.setPaymentType(Schedule::PaymentType::DirectDeposit);
m_schedule.setFixed(true);
m_schedule.setOccurrence(m_generalLoanInfoPage->m_paymentFrequency->currentItem());
MyMoneyTransaction t;
t.setCommodity(m_account.currencyId());
MyMoneySplit s;
// payment split
s.setPayeeId(m_generalLoanInfoPage->m_payee->selectedItem());
s.setAccountId(m_loanSchedulePage->m_paymentAccount->selectedItem());
s.setMemo(i18n("Loan payment"));
s.setValue(m_loanPaymentPage->basePayment() + m_loanPaymentPage->additionalFees());
if (moneyBorrowed()) {
s.setValue(-s.value());
}
s.setShares(s.value());
if (m_account.id() != QLatin1String("Phony-ID")) {
// if the real account is already set perform the currency conversion if it's necessary
MyMoneyMoney paymentShares;
KCurrencyCalculator::setupSplitPrice(paymentShares, t, s, QMap<QString, MyMoneyMoney>(), this);
s.setShares(paymentShares);
}
t.addSplit(s);
// principal split
s.clearId();
s.setAccountId(m_account.id());
s.setShares(MyMoneyMoney::autoCalc);
s.setValue(MyMoneyMoney::autoCalc);
s.setMemo(i18n("Amortization"));
s.setAction(MyMoneySplit::ActionAmortization);
t.addSplit(s);
// interest split
//only add if interest is above zero
if (!m_loanDetailsPage->m_interestRate->value().isZero()) {
s.clearId();
s.setAccountId(m_loanSchedulePage->m_interestCategory->selectedItem());
s.setShares(MyMoneyMoney::autoCalc);
s.setValue(MyMoneyMoney::autoCalc);
s.setMemo(i18n("Interest"));
s.setAction(MyMoneySplit::ActionInterest);
t.addSplit(s);
}
// additional fee splits
QList<MyMoneySplit> additionalSplits;
m_loanPaymentPage->additionalFeesSplits(additionalSplits);
QList<MyMoneySplit>::const_iterator it;
MyMoneyMoney factor(moneyBorrowed() ? 1 : -1, 1);
for (it = additionalSplits.constBegin(); it != additionalSplits.constEnd(); ++it) {
s = (*it);
s.clearId();
s.setShares(s.shares() * factor);
s.setValue(s.value() * factor);
t.addSplit(s);
}
// setup the next due date
t.setPostDate(m_loanSchedulePage->firstPaymentDueDate());
m_schedule.setTransaction(t);
}
}
return m_schedule;
}
MyMoneyMoney Wizard::openingBalance() const
{
// equity accounts don't have an opening balance
- if (m_accountTypePage->accountType() == MyMoneyAccount::Equity)
+ if (m_accountTypePage->accountType() == Account::Equity)
return MyMoneyMoney();
- if (m_accountTypePage->accountType() == MyMoneyAccount::Loan) {
+ if (m_accountTypePage->accountType() == Account::Loan) {
if (m_generalLoanInfoPage->recordAllPayments())
return MyMoneyMoney();
if (moneyBorrowed())
return -(m_generalLoanInfoPage->m_openingBalance->value());
return m_generalLoanInfoPage->m_openingBalance->value();
}
return m_accountTypePage->m_openingBalance->value();
}
MyMoneyPrice Wizard::conversionRate() const
{
if (MyMoneyFile::instance()->baseCurrency().id() == m_accountTypePage->m_currencyComboBox->security().id())
return MyMoneyPrice(MyMoneyFile::instance()->baseCurrency().id(),
m_accountTypePage->m_currencyComboBox->security().id(),
m_accountTypePage->m_openingDate->date(),
MyMoneyMoney::ONE,
i18n("User"));
return MyMoneyPrice(MyMoneyFile::instance()->baseCurrency().id(),
m_accountTypePage->m_currencyComboBox->security().id(),
m_accountTypePage->m_openingDate->date(),
m_accountTypePage->m_conversionRate->value(),
i18n("User"));
}
bool Wizard::moneyBorrowed() const
{
return m_generalLoanInfoPage->m_loanDirection->currentIndex() == 0;
}
class InstitutionPage::Private
{
public:
QList<MyMoneyInstitution> m_list;
};
InstitutionPage::InstitutionPage(Wizard* wizard) :
KInstitutionPageDecl(wizard),
WizardPage<Wizard>(StepInstitution, this, wizard),
d(new Private())
{
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadWidgets()));
connect(m_newInstitutionButton, SIGNAL(clicked()), this, SLOT(slotNewInstitution()));
connect(m_institutionComboBox, SIGNAL(activated(int)), this, SLOT(slotSelectInstitution(int)));
slotLoadWidgets();
m_institutionComboBox->setCurrentItem(0);
slotSelectInstitution(0);
}
InstitutionPage::~InstitutionPage()
{
delete d;
}
void InstitutionPage::slotLoadWidgets()
{
m_institutionComboBox->clear();
d->m_list.clear();
MyMoneyFile::instance()->institutionList(d->m_list);
qSort(d->m_list);
QList<MyMoneyInstitution>::const_iterator it_l;
m_institutionComboBox->addItem("");
for (it_l = d->m_list.constBegin(); it_l != d->m_list.constEnd(); ++it_l) {
m_institutionComboBox->addItem((*it_l).name());
}
}
void InstitutionPage::slotNewInstitution()
{
MyMoneyInstitution institution;
emit m_wizard->createInstitution(institution);
if (!institution.id().isEmpty()) {
QList<MyMoneyInstitution>::const_iterator it_l;
int i = 0;
for (it_l = d->m_list.constBegin(); it_l != d->m_list.constEnd(); ++it_l) {
if ((*it_l).id() == institution.id()) {
// select the item and remember that the very first one is the empty item
m_institutionComboBox->setCurrentIndex(i + 1);
slotSelectInstitution(i + 1);
m_accountNumber->setFocus();
break;
}
++i;
}
}
}
void InstitutionPage::slotSelectInstitution(const int index)
{
m_accountNumber->setEnabled(index != 0);
m_iban->setEnabled(index != 0);
}
void InstitutionPage::selectExistingInstitution(const QString& id)
{
for (int i = 0; i < d->m_list.length(); ++i) {
if (d->m_list[i].id() == id) {
m_institutionComboBox->setCurrentIndex(i + 1);
slotSelectInstitution(i + 1);
break;
}
}
}
const MyMoneyInstitution& InstitutionPage::institution() const
{
static MyMoneyInstitution emptyInstitution;
if (m_institutionComboBox->currentIndex() == 0)
return emptyInstitution;
return d->m_list[m_institutionComboBox->currentIndex()-1];
}
KMyMoneyWizardPage* InstitutionPage::nextPage() const
{
return m_wizard->m_accountTypePage;
}
AccountTypePage::AccountTypePage(Wizard* wizard) :
KAccountTypePageDecl(wizard),
WizardPage<Wizard>(StepAccount, this, wizard),
m_showPriceWarning(true)
{
- m_typeSelection->insertItem(i18n("Checking"), MyMoneyAccount::Checkings);
- m_typeSelection->insertItem(i18n("Savings"), MyMoneyAccount::Savings);
- m_typeSelection->insertItem(i18n("Credit Card"), MyMoneyAccount::CreditCard);
- m_typeSelection->insertItem(i18n("Cash"), MyMoneyAccount::Cash);
- m_typeSelection->insertItem(i18n("Loan"), MyMoneyAccount::Loan);
- m_typeSelection->insertItem(i18n("Investment"), MyMoneyAccount::Investment);
- m_typeSelection->insertItem(i18n("Asset"), MyMoneyAccount::Asset);
- m_typeSelection->insertItem(i18n("Liability"), MyMoneyAccount::Liability);
+ m_typeSelection->insertItem(i18n("Checking"), (int)Account::Checkings);
+ m_typeSelection->insertItem(i18n("Savings"), (int)Account::Savings);
+ m_typeSelection->insertItem(i18n("Credit Card"), (int)Account::CreditCard);
+ m_typeSelection->insertItem(i18n("Cash"), (int)Account::Cash);
+ m_typeSelection->insertItem(i18n("Loan"), (int)Account::Loan);
+ m_typeSelection->insertItem(i18n("Investment"), (int)Account::Investment);
+ m_typeSelection->insertItem(i18n("Asset"), (int)Account::Asset);
+ m_typeSelection->insertItem(i18n("Liability"), (int)Account::Liability);
if (KMyMoneyGlobalSettings::expertMode()) {
- m_typeSelection->insertItem(i18n("Equity"), MyMoneyAccount::Equity);
+ m_typeSelection->insertItem(i18n("Equity"), (int)Account::Equity);
}
- m_typeSelection->setCurrentItem(MyMoneyAccount::Checkings);
+ m_typeSelection->setCurrentItem((int)Account::Checkings);
m_currencyComboBox->setSecurity(MyMoneyFile::instance()->baseCurrency());
m_mandatoryGroup->add(m_accountName);
m_mandatoryGroup->add(m_conversionRate->lineedit());
m_conversionRate->setValue(MyMoneyMoney::ONE);
slotUpdateCurrency();
connect(m_typeSelection, SIGNAL(itemSelected(int)), this, SLOT(slotUpdateType(int)));
connect(m_currencyComboBox, SIGNAL(activated(int)), this, SLOT(slotUpdateCurrency()));
connect(m_conversionRate, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateConversionRate(QString)));
connect(m_conversionRate, SIGNAL(valueChanged(QString)), this, SLOT(slotPriceWarning()));
connect(m_onlineQuote, SIGNAL(clicked()), this, SLOT(slotGetOnlineQuote()));
}
void AccountTypePage::slotUpdateType(int i)
{
- hideShowPages(static_cast<MyMoneyAccount::accountTypeE>(i));
- m_openingBalance->setDisabled(static_cast<MyMoneyAccount::accountTypeE>(i) == MyMoneyAccount::Equity);
+ hideShowPages(static_cast<Account>(i));
+ m_openingBalance->setDisabled(static_cast<Account>(i) == Account::Equity);
}
-void AccountTypePage::hideShowPages(MyMoneyAccount::accountTypeE accountType) const
+void AccountTypePage::hideShowPages(Account accountType) const
{
- bool hideSchedulePage = (accountType != MyMoneyAccount::CreditCard)
- && (accountType != MyMoneyAccount::Loan);
- bool hideLoanPage = (accountType != MyMoneyAccount::Loan);
+ bool hideSchedulePage = (accountType != Account::CreditCard)
+ && (accountType != Account::Loan);
+ bool hideLoanPage = (accountType != Account::Loan);
m_wizard->setStepHidden(StepDetails, hideLoanPage);
m_wizard->setStepHidden(StepPayments, hideLoanPage);
m_wizard->setStepHidden(StepFees, hideLoanPage);
m_wizard->setStepHidden(StepSchedule, hideSchedulePage);
- m_wizard->setStepHidden(StepPayout, (accountType != MyMoneyAccount::Loan));
- m_wizard->setStepHidden(StepBroker, accountType != MyMoneyAccount::Investment);
- m_wizard->setStepHidden(StepParentAccount, accountType == MyMoneyAccount::Loan);
+ m_wizard->setStepHidden(StepPayout, (accountType != Account::Loan));
+ m_wizard->setStepHidden(StepBroker, accountType != Account::Investment);
+ m_wizard->setStepHidden(StepParentAccount, accountType == Account::Loan);
// Force an update of the steps in case the list has changed
m_wizard->reselectStep();
}
KMyMoneyWizardPage* AccountTypePage::nextPage() const
{
- if (accountType() == MyMoneyAccount::Loan)
+ if (accountType() == Account::Loan)
return m_wizard->m_generalLoanInfoPage;
- if (accountType() == MyMoneyAccount::CreditCard)
+ if (accountType() == Account::CreditCard)
return m_wizard->m_schedulePage;
- if (accountType() == MyMoneyAccount::Investment)
+ if (accountType() == Account::Investment)
return m_wizard->m_brokeragepage;
return m_wizard->m_hierarchyPage;
}
void AccountTypePage::slotUpdateCurrency()
{
MyMoneyAccount acc;
acc.setAccountType(accountType());
m_openingBalance->setPrecision(MyMoneyMoney::denomToPrec(acc.fraction(currency())));
bool show = m_currencyComboBox->security().id() != MyMoneyFile::instance()->baseCurrency().id();
m_conversionLabel->setVisible(show);
m_conversionRate->setVisible(show);
m_conversionExample->setVisible(show);
m_onlineQuote->setVisible(show);
m_conversionRate->setEnabled(show); // make sure to include/exclude in mandatoryGroup
m_conversionRate->setPrecision(m_currencyComboBox->security().pricePrecision());
m_mandatoryGroup->changed();
slotUpdateConversionRate(m_conversionRate->lineedit()->text());
}
void AccountTypePage::slotGetOnlineQuote()
{
QString id = MyMoneyFile::instance()->baseCurrency().id() + ' ' + m_currencyComboBox->security().id();
QPointer<KEquityPriceUpdateDlg> dlg = new KEquityPriceUpdateDlg(this, id);
if (dlg->exec() == QDialog::Accepted) {
const MyMoneyPrice &price = dlg->price(id);
if (price.isValid()) {
m_conversionRate->setValue(price.rate(m_currencyComboBox->security().id()));
if (price.date() != m_openingDate->date()) {
priceWarning(true);
}
}
}
delete dlg;
}
void AccountTypePage::slotPriceWarning()
{
priceWarning(false);
}
void AccountTypePage::priceWarning(bool always)
{
if (m_showPriceWarning || always) {
KMessageBox::information(this, i18n("Please make sure to enter the correct conversion for the selected opening date. If you requested an online quote it might be provided for a different date."), i18n("Check date"));
}
m_showPriceWarning = false;
}
void AccountTypePage::slotUpdateConversionRate(const QString& txt)
{
m_conversionExample->setText(i18n("1 %1 equals %2", MyMoneyFile::instance()->baseCurrency().tradingSymbol(), MyMoneyMoney(txt).formatMoney(m_currencyComboBox->security().tradingSymbol(), m_currencyComboBox->security().pricePrecision())));
}
bool AccountTypePage::isComplete() const
{
// check that the conversion rate is positive if enabled
bool rc = !m_conversionRate->isVisible() || (!m_conversionRate->value().isZero() && !m_conversionRate->value().isNegative());
if (!rc) {
m_wizard->m_nextButton->setToolTip(i18n("Conversion rate is not positive"));
} else {
rc = KMyMoneyWizardPage::isComplete();
if (!rc) {
m_wizard->m_nextButton->setToolTip(i18n("No account name supplied"));
}
}
hideShowPages(accountType());
return rc;
}
-MyMoneyAccount::accountTypeE AccountTypePage::accountType() const
+Account AccountTypePage::accountType() const
{
- return static_cast<MyMoneyAccount::accountTypeE>(m_typeSelection->currentItem());
+ return static_cast<Account>(m_typeSelection->currentItem());
}
const MyMoneySecurity& AccountTypePage::currency() const
{
return m_currencyComboBox->security();
}
void AccountTypePage::setAccount(const MyMoneyAccount& acc)
{
- if (acc.accountType() != MyMoneyAccount::UnknownAccountType) {
- if (acc.accountType() == MyMoneyAccount::AssetLoan) {
- m_typeSelection->setCurrentItem(MyMoneyAccount::Loan);
+ if (acc.accountType() != Account::Unknown) {
+ if (acc.accountType() == Account::AssetLoan) {
+ m_typeSelection->setCurrentItem((int)Account::Loan);
} else {
- m_typeSelection->setCurrentItem(acc.accountType());
+ m_typeSelection->setCurrentItem((int)acc.accountType());
}
}
m_openingDate->setDate(acc.openingDate());
m_accountName->setText(acc.name());
}
const MyMoneyAccount& AccountTypePage::parentAccount()
{
switch (accountType()) {
- case MyMoneyAccount::CreditCard:
- case MyMoneyAccount::Liability:
- case MyMoneyAccount::Loan: // Can be either but we return liability here
+ case Account::CreditCard:
+ case Account::Liability:
+ case Account::Loan: // Can be either but we return liability here
return MyMoneyFile::instance()->liability();
break;
- case MyMoneyAccount::Equity:
+ case Account::Equity:
return MyMoneyFile::instance()->equity();
default:
break;
}
return MyMoneyFile::instance()->asset();
}
bool AccountTypePage::allowsParentAccount() const
{
- return accountType() != MyMoneyAccount::Loan;
+ return accountType() != Account::Loan;
}
BrokeragePage::BrokeragePage(Wizard* wizard) :
KBrokeragePageDecl(wizard),
WizardPage<Wizard>(StepBroker, this, wizard)
{
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadWidgets()));
}
void BrokeragePage::slotLoadWidgets()
{
m_brokerageCurrency->update(QString("x"));
}
void BrokeragePage::enterPage()
{
// assign the currency of the investment account to the
// brokerage account if nothing else has ever been selected
if (m_brokerageCurrency->security().id().isEmpty()) {
m_brokerageCurrency->setSecurity(m_wizard->m_accountTypePage->m_currencyComboBox->security());
}
// check if the institution relevant fields should be enabled or not
bool enabled = m_wizard->m_institutionPage->m_accountNumber->isEnabled();
m_accountNumberLabel->setEnabled(enabled);
m_accountNumber->setEnabled(enabled);
m_ibanLabel->setEnabled(enabled);
m_iban->setEnabled(enabled);
}
KMyMoneyWizardPage* BrokeragePage::nextPage() const
{
return m_wizard->m_hierarchyPage;
}
CreditCardSchedulePage::CreditCardSchedulePage(Wizard* wizard) :
KSchedulePageDecl(wizard),
WizardPage<Wizard>(StepSchedule, this, wizard)
{
m_mandatoryGroup->add(m_name);
m_mandatoryGroup->add(m_payee);
m_mandatoryGroup->add(m_amount->lineedit());
m_mandatoryGroup->add(m_paymentAccount);
connect(m_paymentAccount, SIGNAL(itemSelected(QString)), object(), SIGNAL(completeStateChanged()));
connect(m_payee, SIGNAL(itemSelected(QString)), object(), SIGNAL(completeStateChanged()));
connect(m_date, SIGNAL(dateChanged(QDate)), object(), SIGNAL(completeStateChanged()));
connect(m_payee, SIGNAL(createItem(QString,QString&)), wizard, SIGNAL(createPayee(QString,QString&)));
m_reminderCheckBox->setChecked(true);
connect(m_reminderCheckBox, SIGNAL(toggled(bool)), object(), SIGNAL(completeStateChanged()));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadWidgets()));
- m_method->insertItem(i18n("Write check"), MyMoneySchedule::STYPE_WRITECHEQUE);
+ m_method->insertItem(i18n("Write check"), (int)Schedule::PaymentType::WriteChecque);
#if 0
- m_method->insertItem(i18n("Direct debit"), MyMoneySchedule::STYPE_DIRECTDEBIT);
- m_method->insertItem(i18n("Bank transfer"), MyMoneySchedule::STYPE_BANKTRANSFER);
+ m_method->insertItem(i18n("Direct debit"), Schedule::PaymentType::DirectDebit);
+ m_method->insertItem(i18n("Bank transfer"), Schedule::PaymentType::BankTransfer);
#endif
- m_method->insertItem(i18n("Standing order"), MyMoneySchedule::STYPE_STANDINGORDER);
- m_method->insertItem(i18n("Manual deposit"), MyMoneySchedule::STYPE_MANUALDEPOSIT);
- m_method->insertItem(i18n("Direct deposit"), MyMoneySchedule::STYPE_DIRECTDEPOSIT);
- m_method->insertItem(i18nc("Other payment method", "Other"), MyMoneySchedule::STYPE_OTHER);
- m_method->setCurrentItem(MyMoneySchedule::STYPE_DIRECTDEBIT);
+ m_method->insertItem(i18n("Standing order"), (int)Schedule::PaymentType::StandingOrder);
+ m_method->insertItem(i18n("Manual deposit"), (int)Schedule::PaymentType::ManualDeposit);
+ m_method->insertItem(i18n("Direct deposit"), (int)Schedule::PaymentType::DirectDeposit);
+ m_method->insertItem(i18nc("Other payment method", "Other"), (int)Schedule::PaymentType::Other);
+ m_method->setCurrentItem((int)Schedule::PaymentType::DirectDebit);
slotLoadWidgets();
}
void CreditCardSchedulePage::enterPage()
{
if (m_name->text().isEmpty())
m_name->setText(i18n("Credit Card %1 monthly payment", m_wizard->m_accountTypePage->m_accountName->text()));
}
bool CreditCardSchedulePage::isComplete() const
{
bool rc = true;
QString msg = i18n("Finish entry and create account");
if (m_reminderCheckBox->isChecked()) {
msg = i18n("Finish entry and create account and schedule");
if (m_date->date() < m_wizard->m_accountTypePage->m_openingDate->date()) {
rc = false;
msg = i18n("Next due date is prior to opening date");
}
if (m_paymentAccount->selectedItem().isEmpty()) {
rc = false;
msg = i18n("No account selected");
}
if (m_amount->lineedit()->text().isEmpty()) {
rc = false;
msg = i18n("No amount for payment selected");
}
if (m_payee->selectedItem().isEmpty()) {
rc = false;
msg = i18n("No payee for payment selected");
}
if (m_name->text().isEmpty()) {
rc = false;
msg = i18n("No name assigned for schedule");
}
}
m_wizard->m_finishButton->setToolTip(msg);
return rc;
}
void CreditCardSchedulePage::slotLoadWidgets()
{
AccountSet set;
- set.addAccountGroup(MyMoneyAccount::Asset);
+ set.addAccountGroup(Account::Asset);
set.load(m_paymentAccount->selector());
m_payee->loadPayees(MyMoneyFile::instance()->payeeList());
}
KMyMoneyWizardPage* CreditCardSchedulePage::nextPage() const
{
return m_wizard->m_hierarchyPage;
}
GeneralLoanInfoPage::GeneralLoanInfoPage(Wizard* wizard) :
KGeneralLoanInfoPageDecl(wizard),
WizardPage<Wizard>(StepDetails, this, wizard),
m_firstTime(true)
{
m_mandatoryGroup->add(m_payee);
// remove the unsupported payment and compounding frequencies and setup default
- m_paymentFrequency->removeItem(MyMoneySchedule::OCCUR_ONCE);
- m_paymentFrequency->removeItem(MyMoneySchedule::OCCUR_EVERYOTHERYEAR);
- m_paymentFrequency->setCurrentItem(MyMoneySchedule::OCCUR_MONTHLY);
- m_compoundFrequency->removeItem(MyMoneySchedule::OCCUR_ONCE);
- m_compoundFrequency->removeItem(MyMoneySchedule::OCCUR_EVERYOTHERYEAR);
- m_compoundFrequency->setCurrentItem(MyMoneySchedule::OCCUR_MONTHLY);
+ m_paymentFrequency->removeItem((int)Schedule::Occurrence::Once);
+ m_paymentFrequency->removeItem((int)Schedule::Occurrence::EveryOtherYear);
+ m_paymentFrequency->setCurrentItem((int)Schedule::Occurrence::Monthly);
+ m_compoundFrequency->removeItem((int)Schedule::Occurrence::Once);
+ m_compoundFrequency->removeItem((int)Schedule::Occurrence::EveryOtherYear);
+ m_compoundFrequency->setCurrentItem((int)Schedule::Occurrence::Monthly);
slotLoadWidgets();
connect(m_payee, SIGNAL(createItem(QString,QString&)), wizard, SIGNAL(createPayee(QString,QString&)));
connect(m_anyPayments, SIGNAL(activated(int)), object(), SIGNAL(completeStateChanged()));
connect(m_recordings, SIGNAL(activated(int)), object(), SIGNAL(completeStateChanged()));
connect(m_interestType, SIGNAL(activated(int)), object(), SIGNAL(completeStateChanged()));
connect(m_interestChangeDateEdit, SIGNAL(dateChanged(QDate)), object(), SIGNAL(completeStateChanged()));
connect(m_openingBalance, SIGNAL(textChanged(QString)), object(), SIGNAL(completeStateChanged()));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadWidgets()));
}
KMyMoneyWizardPage* GeneralLoanInfoPage::nextPage() const
{
return m_wizard->m_loanDetailsPage;
}
bool GeneralLoanInfoPage::recordAllPayments() const
{
bool rc = true; // all payments
if (m_recordings->isEnabled()) {
if (m_recordings->currentIndex() != 0)
rc = false;
}
return rc;
}
void GeneralLoanInfoPage::enterPage()
{
if (m_firstTime) {
// setup default dates to last of this month and one year on top
QDate firstDay(QDate::currentDate().year(), QDate::currentDate().month(), 1);
m_firstPaymentDate->setDate(firstDay.addMonths(1).addDays(-1));
m_interestChangeDateEdit->setDate(m_firstPaymentDate->date().addYears(1));;
m_firstTime = false;
}
}
bool GeneralLoanInfoPage::isComplete() const
{
m_wizard->setStepHidden(StepPayout, !m_wizard->openingBalance().isZero());
bool rc = KMyMoneyWizardPage::isComplete();
if (!rc) {
m_wizard->m_nextButton->setToolTip(i18n("No payee supplied"));
}
// fixup availability of items on this page
m_recordings->setDisabled(m_anyPayments->currentIndex() == 0);
m_interestFrequencyAmountEdit->setDisabled(m_interestType->currentIndex() == 0);
m_interestFrequencyUnitEdit->setDisabled(m_interestType->currentIndex() == 0);
m_interestChangeDateEdit->setDisabled(m_interestType->currentIndex() == 0);
m_openingBalance->setDisabled(recordAllPayments());
if (m_openingBalance->isEnabled() && m_openingBalance->lineedit()->text().length() == 0) {
rc = false;
m_wizard->m_nextButton->setToolTip(i18n("No opening balance supplied"));
}
if (rc
&& (m_interestType->currentIndex() != 0)
&& (m_interestChangeDateEdit->date() <= m_firstPaymentDate->date())) {
rc = false;
m_wizard->m_nextButton->setToolTip(i18n("An interest change can only happen after the first payment"));
}
return rc;
}
const MyMoneyAccount& GeneralLoanInfoPage::parentAccount()
{
return (m_loanDirection->currentIndex() == 0)
? MyMoneyFile::instance()->liability()
: MyMoneyFile::instance()->asset();
}
void GeneralLoanInfoPage::slotLoadWidgets()
{
m_payee->loadPayees(MyMoneyFile::instance()->payeeList());
}
LoanDetailsPage::LoanDetailsPage(Wizard* wizard) :
KLoanDetailsPageDecl(wizard),
WizardPage<Wizard>(StepPayments, this, wizard),
m_needCalculate(true)
{
// force the balloon payment to zero (default)
m_balloonAmount->setValue(MyMoneyMoney());
// allow any precision for the interest rate
m_interestRate->setPrecision(-1);
connect(m_paymentDue, SIGNAL(activated(int)), this, SLOT(slotValuesChanged()));
connect(m_termAmount, SIGNAL(valueChanged(int)), this, SLOT(slotValuesChanged()));
connect(m_termUnit, SIGNAL(highlighted(int)), this, SLOT(slotValuesChanged()));
connect(m_loanAmount, SIGNAL(textChanged(QString)), this, SLOT(slotValuesChanged()));
connect(m_interestRate, SIGNAL(textChanged(QString)), this, SLOT(slotValuesChanged()));
connect(m_paymentAmount, SIGNAL(textChanged(QString)), this, SLOT(slotValuesChanged()));
connect(m_balloonAmount, SIGNAL(textChanged(QString)), this, SLOT(slotValuesChanged()));
connect(m_calculateButton, SIGNAL(clicked()), this, SLOT(slotCalculate()));
}
void LoanDetailsPage::enterPage()
{
// we need to remove a bunch of entries of the payment frequencies
m_termUnit->clear();
m_mandatoryGroup->clear();
if (!m_wizard->openingBalance().isZero()) {
m_mandatoryGroup->add(m_loanAmount->lineedit());
if (m_loanAmount->lineedit()->text().length() == 0) {
m_loanAmount->setValue(m_wizard->openingBalance().abs());
}
}
switch (m_wizard->m_generalLoanInfoPage->m_paymentFrequency->currentItem()) {
default:
- m_termUnit->insertItem(i18n("Payments"), MyMoneySchedule::OCCUR_ONCE);
- m_termUnit->setCurrentItem(MyMoneySchedule::OCCUR_ONCE);
+ m_termUnit->insertItem(i18n("Payments"), (int)Schedule::Occurrence::Once);
+ m_termUnit->setCurrentItem((int)Schedule::Occurrence::Once);
break;
- case MyMoneySchedule::OCCUR_MONTHLY:
- m_termUnit->insertItem(i18n("Months"), MyMoneySchedule::OCCUR_MONTHLY);
- m_termUnit->insertItem(i18n("Years"), MyMoneySchedule::OCCUR_YEARLY);
- m_termUnit->setCurrentItem(MyMoneySchedule::OCCUR_MONTHLY);
+ case Schedule::Occurrence::Monthly:
+ m_termUnit->insertItem(i18n("Months"), (int)Schedule::Occurrence::Monthly);
+ m_termUnit->insertItem(i18n("Years"), (int)Schedule::Occurrence::Yearly);
+ m_termUnit->setCurrentItem((int)Schedule::Occurrence::Monthly);
break;
- case MyMoneySchedule::OCCUR_YEARLY:
- m_termUnit->insertItem(i18n("Years"), MyMoneySchedule::OCCUR_YEARLY);
- m_termUnit->setCurrentItem(MyMoneySchedule::OCCUR_YEARLY);
+ case Schedule::Occurrence::Yearly:
+ m_termUnit->insertItem(i18n("Years"), (int)Schedule::Occurrence::Yearly);
+ m_termUnit->setCurrentItem((int)Schedule::Occurrence::Yearly);
break;
}
}
void LoanDetailsPage::slotValuesChanged()
{
m_needCalculate = true;
m_wizard->completeStateChanged();
}
void LoanDetailsPage::slotCalculate()
{
MyMoneyFinancialCalculator calc;
double val;
int PF, CF;
QString result;
bool moneyBorrowed = m_wizard->moneyBorrowed();
bool moneyLend = !moneyBorrowed;
// FIXME: for now, we only support interest calculation at the end of the period
calc.setBep();
// FIXME: for now, we only support periodic compounding
calc.setDisc();
PF = m_wizard->m_generalLoanInfoPage->m_paymentFrequency->eventsPerYear();
CF = m_wizard->m_generalLoanInfoPage->m_compoundFrequency->eventsPerYear();
if (PF == 0 || CF == 0)
return;
calc.setPF(PF);
calc.setCF(CF);
if (!m_loanAmount->lineedit()->text().isEmpty()) {
val = m_loanAmount->value().abs().toDouble();
if (moneyBorrowed)
val = -val;
calc.setPv(val);
}
if (!m_interestRate->lineedit()->text().isEmpty()) {
val = m_interestRate->value().abs().toDouble();
calc.setIr(val);
}
if (!m_paymentAmount->lineedit()->text().isEmpty()) {
val = m_paymentAmount->value().abs().toDouble();
if (moneyLend)
val = -val;
calc.setPmt(val);
}
if (!m_balloonAmount->lineedit()->text().isEmpty()) {
val = m_balloonAmount->value().abs().toDouble();
if (moneyLend)
val = -val;
calc.setFv(val);
}
if (m_termAmount->value() != 0) {
calc.setNpp(term());
}
// setup of parameters is done, now do the calculation
try {
if (m_loanAmount->lineedit()->text().isEmpty()) {
// calculate the amount of the loan out of the other information
val = calc.presentValue();
m_loanAmount->loadText(MyMoneyMoney(static_cast<double>(val)).abs().formatMoney("", m_wizard->precision()));
result = i18n("KMyMoney has calculated the amount of the loan as %1.", m_loanAmount->lineedit()->text());
} else if (m_interestRate->lineedit()->text().isEmpty()) {
// calculate the interest rate out of the other information
val = calc.interestRate();
m_interestRate->loadText(MyMoneyMoney(static_cast<double>(val)).abs().formatMoney("", 3));
result = i18n("KMyMoney has calculated the interest rate to %1%.", m_interestRate->lineedit()->text());
} else if (m_paymentAmount->lineedit()->text().isEmpty()) {
// calculate the periodical amount of the payment out of the other information
val = calc.payment();
m_paymentAmount->setValue(MyMoneyMoney(static_cast<double>(val)).abs());
// reset payment as it might have changed due to rounding
val = m_paymentAmount->value().abs().toDouble();
if (moneyLend)
val = -val;
calc.setPmt(val);
result = i18n("KMyMoney has calculated a periodic payment of %1 to cover principal and interest.", m_paymentAmount->lineedit()->text());
val = calc.futureValue();
if ((moneyBorrowed && val < 0 && qAbs(val) >= qAbs(calc.payment()))
|| (moneyLend && val > 0 && qAbs(val) >= qAbs(calc.payment()))) {
calc.setNpp(calc.npp() - 1);
// updateTermWidgets(calc.npp());
val = calc.futureValue();
MyMoneyMoney refVal(static_cast<double>(val));
m_balloonAmount->loadText(refVal.abs().formatMoney("", m_wizard->precision()));
result += QString(" ");
result += i18n("The number of payments has been decremented and the balloon payment has been modified to %1.", m_balloonAmount->lineedit()->text());
} else if ((moneyBorrowed && val < 0 && qAbs(val) < qAbs(calc.payment()))
|| (moneyLend && val > 0 && qAbs(val) < qAbs(calc.payment()))) {
m_balloonAmount->loadText(MyMoneyMoney().formatMoney("", m_wizard->precision()));
} else {
MyMoneyMoney refVal(static_cast<double>(val));
m_balloonAmount->loadText(refVal.abs().formatMoney("", m_wizard->precision()));
result += i18n("The balloon payment has been modified to %1.", m_balloonAmount->lineedit()->text());
}
} else if (m_termAmount->value() == 0) {
// calculate the number of payments out of the other information
val = calc.numPayments();
if (val == 0)
throw MYMONEYEXCEPTION("incorrect fincancial calculation");
// if the number of payments has a fractional part, then we
// round it to the smallest integer and calculate the balloon payment
result = i18n("KMyMoney has calculated the term of your loan as %1. ", updateTermWidgets(qFloor(val)));
if (val != qFloor(val)) {
calc.setNpp(qFloor(val));
val = calc.futureValue();
MyMoneyMoney refVal(static_cast<double>(val));
m_balloonAmount->loadText(refVal.abs().formatMoney("", m_wizard->precision()));
result += i18n("The balloon payment has been modified to %1.", m_balloonAmount->lineedit()->text());
}
} else {
// calculate the future value of the loan out of the other information
val = calc.futureValue();
// we differentiate between the following cases:
// a) the future value is greater than a payment
// b) the future value is less than a payment or the loan is overpaid
// c) all other cases
//
// a) means, we have paid more than we owed. This can't be
// b) means, we paid more than we owed but the last payment is
// less in value than regular payments. That means, that the
// future value is to be treated as (fully payed back)
// c) the loan is not payed back yet
if ((moneyBorrowed && val < 0 && qAbs(val) > qAbs(calc.payment()))
|| (moneyLend && val > 0 && qAbs(val) > qAbs(calc.payment()))) {
// case a)
qDebug("Future Value is %f", val);
throw MYMONEYEXCEPTION("incorrect fincancial calculation");
} else if ((moneyBorrowed && val < 0 && qAbs(val) <= qAbs(calc.payment()))
|| (moneyLend && val > 0 && qAbs(val) <= qAbs(calc.payment()))) {
// case b)
val = 0;
}
MyMoneyMoney refVal(static_cast<double>(val));
result = i18n("KMyMoney has calculated a balloon payment of %1 for this loan.", refVal.abs().formatMoney("", m_wizard->precision()));
if (!m_balloonAmount->lineedit()->text().isEmpty()) {
if ((m_balloonAmount->value().abs() - refVal.abs()).abs().toDouble() > 1) {
throw MYMONEYEXCEPTION("incorrect fincancial calculation");
}
result = i18n("KMyMoney has successfully verified your loan information.");
}
m_balloonAmount->loadText(refVal.abs().formatMoney("", m_wizard->precision()));
}
} catch (const MyMoneyException &) {
KMessageBox::error(0,
i18n("You have entered mis-matching information. Please modify "
"your figures or leave one value empty "
"to let KMyMoney calculate it for you"),
i18n("Calculation error"));
return;
}
result += i18n("\n\nAccept this or modify the loan information and recalculate.");
KMessageBox::information(0, result, i18n("Calculation successful"));
m_needCalculate = false;
// now update change
m_wizard->completeStateChanged();
}
int LoanDetailsPage::term() const
{
int factor = 0;
if (m_termAmount->value() != 0) {
factor = 1;
switch (m_termUnit->currentItem()) {
- case MyMoneySchedule::OCCUR_YEARLY: // years
+ case Schedule::Occurrence::Yearly: // years
factor = 12;
// intentional fall through
- case MyMoneySchedule::OCCUR_MONTHLY: // months
+ case Schedule::Occurrence::Monthly: // months
factor *= 30;
factor *= m_termAmount->value();
// factor now is the duration in days. we divide this by the
// payment frequency and get the number of payments
factor /= m_wizard->m_generalLoanInfoPage->m_paymentFrequency->daysBetweenEvents();
break;
default:
- qDebug("Unknown term unit %d in LoanDetailsPage::term(). Using payments.", m_termUnit->currentItem());
+ qDebug("Unknown term unit %d in LoanDetailsPage::term(). Using payments.", (int)m_termUnit->currentItem());
// intentional fall through
- case MyMoneySchedule::OCCUR_ONCE: // payments
+ case Schedule::Occurrence::Once: // payments
factor = m_termAmount->value();
break;
}
}
return factor;
}
QString LoanDetailsPage::updateTermWidgets(const double val)
{
long vl = qFloor(val);
QString valString;
- MyMoneySchedule::occurrenceE unit = m_termUnit->currentItem();
+ Schedule::Occurrence unit = m_termUnit->currentItem();
- if ((unit == MyMoneySchedule::OCCUR_MONTHLY)
+ if ((unit == Schedule::Occurrence::Monthly)
&& ((vl % 12) == 0)) {
vl /= 12;
- unit = MyMoneySchedule::OCCUR_YEARLY;
+ unit = Schedule::Occurrence::Yearly;
}
switch (unit) {
- case MyMoneySchedule::OCCUR_MONTHLY:
+ case Schedule::Occurrence::Monthly:
valString = i18np("one month", "%1 months", vl);
- m_termUnit->setCurrentItem(MyMoneySchedule::OCCUR_MONTHLY);
+ m_termUnit->setCurrentItem((int)Schedule::Occurrence::Monthly);
break;
- case MyMoneySchedule::OCCUR_YEARLY:
+ case Schedule::Occurrence::Yearly:
valString = i18np("one year", "%1 years", vl);
- m_termUnit->setCurrentItem(MyMoneySchedule::OCCUR_YEARLY);
+ m_termUnit->setCurrentItem((int)Schedule::Occurrence::Yearly);
break;
default:
valString = i18np("one payment", "%1 payments", vl);
- m_termUnit->setCurrentItem(MyMoneySchedule::OCCUR_ONCE);
+ m_termUnit->setCurrentItem((int)Schedule::Occurrence::Once);
break;
}
m_termAmount->setValue(vl);
return valString;
}
bool LoanDetailsPage::isComplete() const
{
// bool rc = KMyMoneyWizardPage::isComplete();
int fieldCnt = 0;
if (m_loanAmount->lineedit()->text().length() > 0) {
fieldCnt++;
}
if (m_interestRate->lineedit()->text().length() > 0) {
fieldCnt++;
}
if (m_termAmount->value() != 0) {
fieldCnt++;
}
if (m_paymentAmount->lineedit()->text().length() > 0) {
fieldCnt++;
}
if (m_balloonAmount->lineedit()->text().length() > 0) {
fieldCnt++;
}
m_calculateButton->setEnabled(fieldCnt == 4 || (fieldCnt == 5 && m_needCalculate));
m_calculateButton->setAutoDefault(false);
m_calculateButton->setDefault(false);
if (m_needCalculate && fieldCnt == 4) {
m_wizard->m_nextButton->setToolTip(i18n("Press Calculate to verify the values"));
m_calculateButton->setAutoDefault(true);
m_calculateButton->setDefault(true);
} else if (fieldCnt != 5) {
m_wizard->m_nextButton->setToolTip(i18n("Not all details supplied"));
m_calculateButton->setAutoDefault(true);
m_calculateButton->setDefault(true);
}
m_wizard->m_nextButton->setAutoDefault(!m_calculateButton->autoDefault());
m_wizard->m_nextButton->setDefault(!m_calculateButton->autoDefault());
return (fieldCnt == 5) && !m_needCalculate;
}
KMyMoneyWizardPage* LoanDetailsPage::nextPage() const
{
return m_wizard->m_loanPaymentPage;
}
class LoanPaymentPage::Private
{
public:
MyMoneyAccount phonyAccount;
MyMoneySplit phonySplit;
MyMoneyTransaction additionalFeesTransaction;
MyMoneyMoney additionalFees;
};
LoanPaymentPage::LoanPaymentPage(Wizard* wizard) :
KLoanPaymentPageDecl(wizard),
WizardPage<Wizard>(StepFees, this, wizard),
d(new Private)
{
d->phonyAccount = MyMoneyAccount(QLatin1String("Phony-ID"), MyMoneyAccount());
d->phonySplit.setAccountId(d->phonyAccount.id());
d->phonySplit.setValue(MyMoneyMoney());
d->phonySplit.setShares(MyMoneyMoney());
d->additionalFeesTransaction.addSplit(d->phonySplit);
connect(m_additionalFeesButton, SIGNAL(clicked()), this, SLOT(slotAdditionalFees()));
}
LoanPaymentPage::~LoanPaymentPage()
{
delete d;
}
MyMoneyMoney LoanPaymentPage::basePayment() const
{
return m_wizard->m_loanDetailsPage->m_paymentAmount->value();
}
MyMoneyMoney LoanPaymentPage::additionalFees() const
{
return d->additionalFees;
}
void LoanPaymentPage::additionalFeesSplits(QList<MyMoneySplit>& list)
{
list.clear();
QList<MyMoneySplit>::ConstIterator it;
for (it = d->additionalFeesTransaction.splits().constBegin(); it != d->additionalFeesTransaction.splits().constEnd(); ++it) {
if ((*it).accountId() != d->phonyAccount.id()) {
list << (*it);
}
}
}
void LoanPaymentPage::updateAmounts()
{
m_additionalFees->setText(d->additionalFees.formatMoney(m_wizard->currency().tradingSymbol(), m_wizard->precision()));
m_totalPayment->setText((basePayment() + d->additionalFees).formatMoney(m_wizard->currency().tradingSymbol(), m_wizard->precision()));
}
void LoanPaymentPage::enterPage()
{
const MyMoneySecurity& currency = m_wizard->currency();
m_basePayment->setText(basePayment().formatMoney(currency.tradingSymbol(), m_wizard->precision()));
d->phonyAccount.setCurrencyId(currency.id());
d->additionalFeesTransaction.setCommodity(currency.id());
updateAmounts();
}
void LoanPaymentPage::slotAdditionalFees()
{
QMap<QString, MyMoneyMoney> priceInfo;
QPointer<KSplitTransactionDlg> dlg = new KSplitTransactionDlg(d->additionalFeesTransaction, d->phonySplit, d->phonyAccount, false, !m_wizard->moneyBorrowed(), MyMoneyMoney(), priceInfo);
// connect(dlg, SIGNAL(newCategory(MyMoneyAccount&)), this, SIGNAL(newCategory(MyMoneyAccount&)));
if (dlg->exec() == QDialog::Accepted) {
d->additionalFeesTransaction = dlg->transaction();
// sum up the additional fees
QList<MyMoneySplit>::ConstIterator it;
d->additionalFees = MyMoneyMoney();
for (it = d->additionalFeesTransaction.splits().constBegin(); it != d->additionalFeesTransaction.splits().constEnd(); ++it) {
if ((*it).accountId() != d->phonyAccount.id()) {
d->additionalFees += (*it).shares();
}
}
updateAmounts();
}
delete dlg;
}
KMyMoneyWizardPage* LoanPaymentPage::nextPage() const
{
return m_wizard->m_loanSchedulePage;
}
LoanSchedulePage::LoanSchedulePage(Wizard* wizard) :
KLoanSchedulePageDecl(wizard),
WizardPage<Wizard>(StepSchedule, this, wizard)
{
m_mandatoryGroup->add(m_interestCategory->lineEdit());
m_mandatoryGroup->add(m_paymentAccount->lineEdit());
connect(m_interestCategory, SIGNAL(createItem(QString,QString&)), this, SLOT(slotCreateCategory(QString,QString&)));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadWidgets()));
}
void LoanSchedulePage::slotCreateCategory(const QString& name, QString& id)
{
MyMoneyAccount acc, parent;
acc.setName(name);
if (m_wizard->moneyBorrowed())
parent = MyMoneyFile::instance()->expense();
else
parent = MyMoneyFile::instance()->income();
emit m_wizard->createCategory(acc, parent);
// return id
id = acc.id();
}
QDate LoanSchedulePage::firstPaymentDueDate() const
{
if (m_firstPaymentDueDate->isEnabled())
return m_firstPaymentDueDate->date();
return m_wizard->m_generalLoanInfoPage->m_firstPaymentDate->date();
}
void LoanSchedulePage::enterPage()
{
m_interestCategory->setFocus();
m_firstPaymentDueDate->setDisabled(m_wizard->m_generalLoanInfoPage->recordAllPayments());
slotLoadWidgets();
}
void LoanSchedulePage::slotLoadWidgets()
{
AccountSet set;
if (m_wizard->moneyBorrowed())
- set.addAccountGroup(MyMoneyAccount::Expense);
+ set.addAccountGroup(Account::Expense);
else
- set.addAccountGroup(MyMoneyAccount::Income);
+ set.addAccountGroup(Account::Income);
set.load(m_interestCategory->selector());
set.clear();
- set.addAccountGroup(MyMoneyAccount::Asset);
+ set.addAccountGroup(Account::Asset);
set.load(m_paymentAccount->selector());
}
KMyMoneyWizardPage* LoanSchedulePage::nextPage() const
{
// if the balance widget of the general loan info page is enabled and
// the value is not zero, then the payout already happened and we don't
// aks for it.
if (m_wizard->openingBalance().isZero())
return m_wizard->m_loanPayoutPage;
return m_wizard->m_accountSummaryPage;
}
LoanPayoutPage::LoanPayoutPage(Wizard* wizard) :
KLoanPayoutPageDecl(wizard),
WizardPage<Wizard>(StepPayout, this, wizard)
{
m_mandatoryGroup->add(m_assetAccount->lineEdit());
m_mandatoryGroup->add(m_loanAccount->lineEdit());
KGuiItem createAssetButtenItem(i18n("&Create..."),
QIcon::fromTheme(g_Icons[Icon::DocumentNew]),
i18n("Create a new asset account"),
i18n("If the asset account does not yet exist, press this button to create it."));
KGuiItem::assign(m_createAssetButton, createAssetButtenItem);
m_createAssetButton->setToolTip(createAssetButtenItem.toolTip());
m_createAssetButton->setWhatsThis(createAssetButtenItem.whatsThis());
connect(m_createAssetButton, SIGNAL(clicked()), this, SLOT(slotCreateAssetAccount()));
connect(m_noPayoutTransaction, SIGNAL(toggled(bool)), this, SLOT(slotButtonsToggled()));
connect(m_refinanceLoan, SIGNAL(toggled(bool)), this, SLOT(slotButtonsToggled()));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadWidgets()));
slotLoadWidgets();
}
void LoanPayoutPage::slotButtonsToggled()
{
// we don't go directly, as the order of the emission of signals to slots is
// not defined. Using a single shot timer postpones the call of m_mandatoryGroup::changed()
// until the next round of the main loop so we can be sure to see all relevant changes
// that happened in the meantime (eg. widgets are enabled and disabled)
QTimer::singleShot(0, m_mandatoryGroup, SLOT(changed()));
}
void LoanPayoutPage::slotCreateAssetAccount()
{
MyMoneyAccount acc;
- acc.setAccountType(MyMoneyAccount::Asset);
+ acc.setAccountType(Account::Asset);
acc.setOpeningDate(m_wizard->m_accountTypePage->m_openingDate->date());
emit m_wizard->createAccount(acc);
if (!acc.id().isEmpty()) {
m_assetAccount->setSelectedItem(acc.id());
}
}
void LoanPayoutPage::slotLoadWidgets()
{
AccountSet set;
- set.addAccountGroup(MyMoneyAccount::Asset);
+ set.addAccountGroup(Account::Asset);
set.load(m_assetAccount->selector());
set.clear();
- set.addAccountType(MyMoneyAccount::Loan);
+ set.addAccountType(Account::Loan);
set.load(m_loanAccount->selector());
}
void LoanPayoutPage::enterPage()
{
// only allow to create new asset accounts for liability loans
m_createAssetButton->setEnabled(m_wizard->moneyBorrowed());
m_refinanceLoan->setEnabled(m_wizard->moneyBorrowed());
if (!m_wizard->moneyBorrowed()) {
m_refinanceLoan->setChecked(false);
}
m_payoutDetailFrame->setDisabled(m_noPayoutTransaction->isChecked());
}
KMyMoneyWizardPage* LoanPayoutPage::nextPage() const
{
return m_wizard->m_accountSummaryPage;
}
bool LoanPayoutPage::isComplete() const
{
return KMyMoneyWizardPage::isComplete() | m_noPayoutTransaction->isChecked();
}
const QString& LoanPayoutPage::payoutAccountId() const
{
if (m_refinanceLoan->isChecked()) {
return m_loanAccount->selectedItem();
} else {
return m_assetAccount->selectedItem();
}
}
HierarchyFilterProxyModel::HierarchyFilterProxyModel(QObject *parent)
: AccountsProxyModel(parent)
{
}
/**
* Filter the favorites accounts group.
*/
bool HierarchyFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (!source_parent.isValid()) {
auto accCol = m_mdlColumns->indexOf(eAccountsModel::Column::Account);
QVariant data = sourceModel()->index(source_row, accCol, source_parent).data((int)eAccountsModel::Role::ID);
if (data.isValid() && data.toString() == AccountsModel::favoritesAccountId)
return false;
}
return AccountsProxyModel::filterAcceptsRow(source_row, source_parent);
}
/**
* Filter all but the first column.
*/
bool HierarchyFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
if (source_column == 0)
return true;
return false;
}
HierarchyPage::HierarchyPage(Wizard* wizard) :
KHierarchyPageDecl(wizard),
WizardPage<Wizard>(StepParentAccount, this, wizard),
m_filterProxyModel(nullptr)
{
// the proxy filter model
m_filterProxyModel = new HierarchyFilterProxyModel(this);
m_filterProxyModel->setHideClosedAccounts(true);
m_filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode());
- m_filterProxyModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {MyMoneyAccount::Asset, MyMoneyAccount::Liability});
+ m_filterProxyModel->addAccountGroup(QVector<Account> {Account::Asset, Account::Liability});
auto const model = Models::instance()->accountsModel();
m_filterProxyModel->setSourceModel(model);
m_filterProxyModel->setSourceColumns(model->getColumns());
m_filterProxyModel->setDynamicSortFilter(true);
m_parentAccounts->setModel(m_filterProxyModel);
m_parentAccounts->sortByColumn((int)eAccountsModel::Column::Account, Qt::AscendingOrder);
connect(m_parentAccounts->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(parentAccountChanged()));
}
void HierarchyPage::enterPage()
{
// Ensure that the list reflects the Account Type
MyMoneyAccount topAccount = m_wizard->m_accountTypePage->parentAccount();
m_filterProxyModel->clear();
- m_filterProxyModel->addAccountGroup(QVector<MyMoneyAccount::_accountTypeE> {topAccount.accountGroup()});
+ m_filterProxyModel->addAccountGroup(QVector<Account> {topAccount.accountGroup()});
m_parentAccounts->expandAll();
}
KMyMoneyWizardPage* HierarchyPage::nextPage() const
{
return m_wizard->m_accountSummaryPage;
}
const MyMoneyAccount& HierarchyPage::parentAccount()
{
QVariant data = m_parentAccounts->model()->data(m_parentAccounts->currentIndex(), (int)eAccountsModel::Role::Account);
if (data.isValid()) {
m_parentAccount = data.value<MyMoneyAccount>();
} else {
m_parentAccount = MyMoneyAccount();
}
return m_parentAccount;
}
bool HierarchyPage::isComplete() const
{
return m_parentAccounts->currentIndex().isValid();
}
void HierarchyPage::parentAccountChanged()
{
completeStateChanged();
}
AccountSummaryPage::AccountSummaryPage(Wizard* wizard) :
KAccountSummaryPageDecl(wizard),
WizardPage<Wizard>(StepFinish, this, wizard)
{
}
void AccountSummaryPage::enterPage()
{
MyMoneyAccount acc = m_wizard->account();
MyMoneySecurity sec = m_wizard->currency();
acc.fraction(sec);
// assign an id to the account inside the wizard which is required for a schedule
// get the schedule and clear the id again in the wizards object.
MyMoneyAccount tmp(QLatin1String("Phony-ID"), acc);
m_wizard->setAccount(tmp);
MyMoneySchedule sch = m_wizard->schedule();
m_wizard->setAccount(acc);
m_dataList->clear();
// Account data
m_dataList->setFontWeight(QFont::Bold);
m_dataList->append(i18n("Account information"));
m_dataList->setFontWeight(QFont::Normal);
m_dataList->append(i18nc("Account name", "Name: %1", acc.name()));
if (!acc.isLoan())
m_dataList->append(i18n("Subaccount of %1", m_wizard->parentAccount().name()));
QString accTypeText;
- if (acc.accountType() == MyMoneyAccount::AssetLoan)
+ if (acc.accountType() == Account::AssetLoan)
accTypeText = i18n("Loan");
else
accTypeText = m_wizard->m_accountTypePage->m_typeSelection->currentText();
m_dataList->append(i18n("Type: %1", accTypeText));
m_dataList->append(i18n("Currency: %1", m_wizard->currency().name()));
m_dataList->append(i18n("Opening date: %1", QLocale().toString(acc.openingDate())));
if (m_wizard->currency().id() != MyMoneyFile::instance()->baseCurrency().id()) {
m_dataList->append(i18n("Conversion rate: %1", m_wizard->conversionRate().rate(QString()).formatMoney("", m_wizard->currency().pricePrecision())));
}
if (!acc.isLoan() || !m_wizard->openingBalance().isZero())
m_dataList->append(i18n("Opening balance: %1", MyMoneyUtils::formatMoney(m_wizard->openingBalance(), acc, sec)));
if (!m_wizard->m_institutionPage->institution().id().isEmpty()) {
m_dataList->append(i18n("Institution: %1", m_wizard->m_institutionPage->institution().name()));
if (!acc.number().isEmpty()) {
m_dataList->append(i18n("Number: %1", acc.number()));
}
if (!acc.value("iban").isEmpty()) {
m_dataList->append(i18n("IBAN: %1", acc.value("iban")));
}
}
- if (acc.accountType() == MyMoneyAccount::Investment) {
+ if (acc.accountType() == Account::Investment) {
if (m_wizard->m_brokeragepage->m_createBrokerageButton->isChecked()) {
m_dataList->setFontWeight(QFont::Bold);
m_dataList->append(i18n("Brokerage Account"));
m_dataList->setFontWeight(QFont::Normal);
m_dataList->append(i18nc("Account name", "Name: %1 (Brokerage)", acc.name()));
m_dataList->append(i18n("Currency: %1", m_wizard->m_brokeragepage->m_brokerageCurrency->security().name()));
if (m_wizard->m_brokeragepage->m_accountNumber->isEnabled() && !m_wizard->m_brokeragepage->m_accountNumber->text().isEmpty())
m_dataList->append(i18n("Number: %1", m_wizard->m_brokeragepage->m_accountNumber->text()));
if (m_wizard->m_brokeragepage->m_iban->isEnabled() && !m_wizard->m_brokeragepage->m_iban->text().isEmpty())
m_dataList->append(i18n("IBAN: %1", m_wizard->m_brokeragepage->m_iban->text()));
}
}
// Loan
if (acc.isLoan()) {
m_dataList->setFontWeight(QFont::Bold);
m_dataList->append(i18n("Loan information"));
m_dataList->setFontWeight(QFont::Normal);
if (m_wizard->moneyBorrowed()) {
m_dataList->append(i18n("Amount borrowed: %1", m_wizard->m_loanDetailsPage->m_loanAmount->value().formatMoney(m_wizard->currency().tradingSymbol(), m_wizard->precision())));
} else {
m_dataList->append(i18n("Amount lent: %1", m_wizard->m_loanDetailsPage->m_loanAmount->value().formatMoney(m_wizard->currency().tradingSymbol(), m_wizard->precision())));
}
m_dataList->append(i18n("Interest rate: %1 %", m_wizard->m_loanDetailsPage->m_interestRate->value().formatMoney("", -1)));
m_dataList->append(i18n("Interest rate is %1", m_wizard->m_generalLoanInfoPage->m_interestType->currentText()));
m_dataList->append(i18n("Principal and interest: %1", MyMoneyUtils::formatMoney(m_wizard->m_loanDetailsPage->m_paymentAmount->value(), acc, sec)));
m_dataList->append(i18n("Additional Fees: %1", MyMoneyUtils::formatMoney(m_wizard->m_loanPaymentPage->additionalFees(), acc, sec)));
m_dataList->append(i18n("Payment frequency: %1", m_wizard->m_generalLoanInfoPage->m_paymentFrequency->currentText()));
m_dataList->append(i18n("Payment account: %1", m_wizard->m_loanSchedulePage->m_paymentAccount->currentText()));
if (!m_wizard->m_loanPayoutPage->m_noPayoutTransaction->isChecked() && m_wizard->openingBalance().isZero()) {
m_dataList->setFontWeight(QFont::Bold);
m_dataList->append(i18n("Payout information"));
m_dataList->setFontWeight(QFont::Normal);
if (m_wizard->m_loanPayoutPage->m_refinanceLoan->isChecked()) {
m_dataList->append(i18n("Refinance: %1", m_wizard->m_loanPayoutPage->m_loanAccount->currentText()));
} else {
if (m_wizard->moneyBorrowed())
m_dataList->append(i18n("Transfer amount to %1", m_wizard->m_loanPayoutPage->m_assetAccount->currentText()));
else
m_dataList->append(i18n("Transfer amount from %1", m_wizard->m_loanPayoutPage->m_assetAccount->currentText()));
}
m_dataList->append(i18n("Payment date: %1 ", QLocale().toString(m_wizard->m_loanPayoutPage->m_payoutDate->date())));
}
}
// Schedule
if (!(sch == MyMoneySchedule())) {
m_dataList->setFontWeight(QFont::Bold);
m_dataList->append(i18n("Schedule information"));
m_dataList->setFontWeight(QFont::Normal);
m_dataList->append(i18nc("Schedule name", "Name: %1", sch.name()));
- if (acc.accountType() == MyMoneyAccount::CreditCard) {
+ if (acc.accountType() == Account::CreditCard) {
MyMoneyAccount paymentAccount = MyMoneyFile::instance()->account(m_wizard->m_schedulePage->m_paymentAccount->selectedItem());
m_dataList->append(i18n("Occurrence: Monthly"));
m_dataList->append(i18n("Paid from %1", paymentAccount.name()));
m_dataList->append(i18n("Pay to %1", m_wizard->m_schedulePage->m_payee->currentText()));
m_dataList->append(i18n("Amount: %1", MyMoneyUtils::formatMoney(m_wizard->m_schedulePage->m_amount->value(), acc, sec)));
m_dataList->append(i18n("First payment due on %1", QLocale().toString(sch.nextDueDate())));
m_dataList->append(i18n("Payment method: %1", m_wizard->m_schedulePage->m_method->currentText()));
}
if (acc.isLoan()) {
m_dataList->append(i18n("Occurrence: %1", m_wizard->m_generalLoanInfoPage->m_paymentFrequency->currentText()));
m_dataList->append(i18n("Amount: %1", MyMoneyUtils::formatMoney(m_wizard->m_loanPaymentPage->basePayment() + m_wizard->m_loanPaymentPage->additionalFees(), acc, sec)));
m_dataList->append(i18n("First payment due on %1", QLocale().toString(m_wizard->m_loanSchedulePage->firstPaymentDueDate())));
}
}
}
}
diff --git a/kmymoney/wizards/newaccountwizard/knewaccountwizard_p.h b/kmymoney/wizards/newaccountwizard/knewaccountwizard_p.h
index 77ebf1cf5..e163b0a3d 100644
--- a/kmymoney/wizards/newaccountwizard/knewaccountwizard_p.h
+++ b/kmymoney/wizards/newaccountwizard/knewaccountwizard_p.h
@@ -1,454 +1,454 @@
/***************************************************************************
knewaccountwizard_p.h
-------------------
begin : Tue Sep 25 2007
copyright : (C) 2007 Thomas Baumgart
email : Thomas Baumgart <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 KNEWACCOUNTWIZARD_P_H
#define KNEWACCOUNTWIZARD_P_H
// ----------------------------------------------------------------------------
// QT Includes
#include <QCheckBox>
#include <QList>
// ----------------------------------------------------------------------------
// KDE Includes
#include <kcombobox.h>
#include <klineedit.h>
// ----------------------------------------------------------------------------
// Project Includes
#include <kmymoneywizard.h>
#include <kmymoneydateinput.h>
#include <kmymoneycurrencyselector.h>
#include <mymoneyaccount.h>
#include <kmymoneyedit.h>
#include "accountsproxymodel.h"
#include "ui_kinstitutionpagedecl.h"
#include "ui_kaccounttypepagedecl.h"
#include "ui_kbrokeragepagedecl.h"
#include "ui_kschedulepagedecl.h"
#include "ui_kgeneralloaninfopagedecl.h"
#include "ui_kloandetailspagedecl.h"
#include "ui_kloanpaymentpagedecl.h"
#include "ui_kloanschedulepagedecl.h"
#include "ui_kloanpayoutpagedecl.h"
#include "ui_khierarchypagedecl.h"
#include "ui_kaccountsummarypagedecl.h"
class Wizard;
class MyMoneyInstitution;
namespace NewAccountWizard
{
class KInstitutionPageDecl : public QWidget, public Ui::KInstitutionPageDecl
{
public:
KInstitutionPageDecl(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class InstitutionPage : public KInstitutionPageDecl, public WizardPage<Wizard>
{
Q_OBJECT
public:
InstitutionPage(Wizard* parent);
~InstitutionPage();
KMyMoneyWizardPage* nextPage(void) const;
QWidget* initialFocusWidget(void) const {
return m_institutionComboBox;
}
/**
* Returns the information about an institution if entered by
* the user. If the id field is empty, then he did not enter
* such information.
*/
const MyMoneyInstitution& institution(void) const;
void selectExistingInstitution(const QString& id);
private slots:
void slotLoadWidgets(void);
void slotNewInstitution(void);
void slotSelectInstitution(int index);
private:
/// \internal d-pointer class.
class Private;
/// \internal d-pointer instance.
Private* const d;
};
class KAccountTypePageDecl : public QWidget, public Ui::KAccountTypePageDecl
{
public:
KAccountTypePageDecl(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class AccountTypePage : public KAccountTypePageDecl, public WizardPage<Wizard>
{
Q_OBJECT
public:
AccountTypePage(Wizard* parent);
virtual bool isComplete(void) const;
KMyMoneyWizardPage* nextPage(void) const;
QWidget* initialFocusWidget(void) const {
return m_accountName;
}
- MyMoneyAccount::accountTypeE accountType(void) const;
+ eMyMoney::Account accountType(void) const;
const MyMoneyAccount& parentAccount(void);
bool allowsParentAccount(void) const;
const MyMoneySecurity& currency(void) const;
void setAccount(const MyMoneyAccount& acc);
private:
- void hideShowPages(MyMoneyAccount::accountTypeE i) const;
+ void hideShowPages(eMyMoney::Account i) const;
void priceWarning(bool);
private slots:
void slotUpdateType(int i);
void slotUpdateCurrency(void);
void slotUpdateConversionRate(const QString&);
void slotGetOnlineQuote(void);
void slotPriceWarning(void);
private:
bool m_showPriceWarning;
};
class KBrokeragePageDecl : public QWidget, public Ui::KBrokeragePageDecl
{
public:
KBrokeragePageDecl(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class BrokeragePage : public KBrokeragePageDecl, public WizardPage<Wizard>
{
Q_OBJECT
public:
BrokeragePage(Wizard* parent);
KMyMoneyWizardPage* nextPage(void) const;
void enterPage(void);
QWidget* initialFocusWidget(void) const {
return m_createBrokerageButton;
}
private slots:
void slotLoadWidgets(void);
};
class KSchedulePageDecl : public QWidget, public Ui::KSchedulePageDecl
{
public:
KSchedulePageDecl(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class CreditCardSchedulePage : public KSchedulePageDecl, public WizardPage<Wizard>
{
Q_OBJECT
public:
CreditCardSchedulePage(Wizard* parent);
KMyMoneyWizardPage* nextPage(void) const;
virtual bool isComplete(void) const;
void enterPage(void);
QWidget* initialFocusWidget(void) const {
return m_reminderCheckBox;
}
private slots:
void slotLoadWidgets(void);
};
class KGeneralLoanInfoPageDecl : public QWidget, public Ui::KGeneralLoanInfoPageDecl
{
public:
KGeneralLoanInfoPageDecl(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class GeneralLoanInfoPage : public KGeneralLoanInfoPageDecl, public WizardPage<Wizard>
{
Q_OBJECT
public:
GeneralLoanInfoPage(Wizard* parent);
KMyMoneyWizardPage* nextPage(void) const;
virtual bool isComplete(void) const;
void enterPage(void);
const MyMoneyAccount& parentAccount(void);
QWidget* initialFocusWidget(void) const {
return m_loanDirection;
}
/**
* Returns @p true if the user decided to record all payments, @p false otherwise.
*/
bool recordAllPayments(void) const;
private slots:
void slotLoadWidgets(void);
private:
bool m_firstTime;
};
class KLoanDetailsPageDecl : public QWidget, public Ui::KLoanDetailsPageDecl
{
public:
KLoanDetailsPageDecl(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class LoanDetailsPage : public KLoanDetailsPageDecl, public WizardPage<Wizard>
{
Q_OBJECT
public:
LoanDetailsPage(Wizard* parent);
void enterPage(void);
KMyMoneyWizardPage* nextPage(void) const;
virtual bool isComplete(void) const;
QWidget* initialFocusWidget(void) const {
return m_paymentDue;
}
/**
* This method returns the number of payments depending on
* the settings of m_termAmount and m_termUnit widgets
*/
int term(void) const;
private:
/**
* This method is used to update the term widgets
* according to the length of the given @a term.
* The term is also converted into a string and returned.
*/
QString updateTermWidgets(const double term);
private:
bool m_needCalculate;
private slots:
void slotValuesChanged(void);
void slotCalculate(void);
};
class KLoanPaymentPageDecl : public QWidget, public Ui::KLoanPaymentPageDecl
{
public:
KLoanPaymentPageDecl(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class LoanPaymentPage : public KLoanPaymentPageDecl, public WizardPage<Wizard>
{
Q_OBJECT
public:
LoanPaymentPage(Wizard* parent);
~LoanPaymentPage();
KMyMoneyWizardPage* nextPage(void) const;
void enterPage(void);
/**
* This method returns the sum of the additional fees
*/
MyMoneyMoney additionalFees(void) const;
/**
* This method returns the base payment, that's principal and interest
*/
MyMoneyMoney basePayment(void) const;
/**
* This method returns the splits that make up the additional fees in @p list.
* @note The splits may contain assigned ids which the caller must remove before
* adding the splits to a MyMoneyTransaction object.
*/
void additionalFeesSplits(QList<MyMoneySplit>& list);
protected slots:
void slotAdditionalFees(void);
protected:
void updateAmounts(void);
private:
/// \internal d-pointer class.
class Private;
/// \internal d-pointer instance.
Private* const d;
};
class KLoanSchedulePageDecl : public QWidget, public Ui::KLoanSchedulePageDecl
{
public:
KLoanSchedulePageDecl(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class LoanSchedulePage : public KLoanSchedulePageDecl, public WizardPage<Wizard>
{
Q_OBJECT
public:
LoanSchedulePage(Wizard* parent);
void enterPage(void);
KMyMoneyWizardPage* nextPage(void) const;
/**
* This method returns the due date of the first payment to be recorded.
*/
QDate firstPaymentDueDate(void) const;
QWidget* initialFocusWidget(void) const {
return m_interestCategory;
}
private slots:
void slotLoadWidgets(void);
void slotCreateCategory(const QString& name, QString& id);
};
class KLoanPayoutPageDecl : public QWidget, public Ui::KLoanPayoutPageDecl
{
public:
KLoanPayoutPageDecl(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class LoanPayoutPage : public KLoanPayoutPageDecl, public WizardPage<Wizard>
{
Q_OBJECT
public:
LoanPayoutPage(Wizard* parent);
void enterPage(void);
virtual bool isComplete(void) const;
KMyMoneyWizardPage* nextPage(void) const;
QWidget* initialFocusWidget(void) const {
return m_noPayoutTransaction;
}
const QString& payoutAccountId(void) const;
private slots:
void slotLoadWidgets(void);
void slotCreateAssetAccount(void);
void slotButtonsToggled(void);
};
class HierarchyFilterProxyModel : public AccountsProxyModel
{
Q_OBJECT
public:
HierarchyFilterProxyModel(QObject *parent = 0);
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const;
};
class KHierarchyPageDecl : public QWidget, public Ui::KHierarchyPageDecl
{
public:
KHierarchyPageDecl(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class HierarchyPage : public KHierarchyPageDecl, public WizardPage<Wizard>
{
Q_OBJECT
public:
HierarchyPage(Wizard* parent);
void enterPage(void);
KMyMoneyWizardPage* nextPage(void) const;
QWidget* initialFocusWidget(void) const {
return m_parentAccounts;
}
const MyMoneyAccount& parentAccount(void);
bool isComplete(void) const;
protected slots:
void parentAccountChanged();
private:
HierarchyFilterProxyModel *m_filterProxyModel;
MyMoneyAccount m_parentAccount;
};
class KAccountSummaryPageDecl : public QWidget, public Ui::KAccountSummaryPageDecl
{
public:
KAccountSummaryPageDecl(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class AccountSummaryPage : public KAccountSummaryPageDecl, public WizardPage<Wizard>
{
Q_OBJECT
public:
AccountSummaryPage(Wizard* parent);
void enterPage(void);
QWidget* initialFocusWidget(void) const {
return m_dataList;
}
};
} // namespace
#endif
diff --git a/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp b/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp
index 5a018e53b..ef3c9ad26 100644
--- a/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp
+++ b/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp
@@ -1,236 +1,236 @@
/***************************************************************************
knewinvestmentwizard - description
-------------------
begin : Sat Dec 4 2004
copyright : (C) 2004 by Thomas Baumgart
email : kmymoney-devel@kde.org
***************************************************************************/
/***************************************************************************
* *
* 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 "knewinvestmentwizard.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KMessageBox>
#include <KHelpClient>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneysecurity.h"
#include "mymoneyfile.h"
#include "webpricequote.h"
#include "kmymoneyutils.h"
KNewInvestmentWizard::KNewInvestmentWizard(QWidget *parent) :
KNewInvestmentWizardDecl(parent)
{
init1();
m_onlineUpdatePage->slotCheckPage(QString());
m_investmentDetailsPage->setupInvestmentSymbol();
connect(m_investmentDetailsPage, SIGNAL(checkForExistingSymbol(QString)), this, SLOT(slotCheckForExistingSymbol(QString)));
}
KNewInvestmentWizard::KNewInvestmentWizard(const MyMoneyAccount& acc, QWidget *parent) :
KNewInvestmentWizardDecl(parent),
m_account(acc)
{
setWindowTitle(i18n("Investment detail wizard"));
init1();
// load the widgets with the data
setName(m_account.name());
m_security = MyMoneyFile::instance()->security(m_account.currencyId());
init2();
int priceMode = 0;
if (!m_account.value("priceMode").isEmpty())
priceMode = m_account.value("priceMode").toInt();
m_investmentDetailsPage->setCurrentPriceMode(priceMode);
}
KNewInvestmentWizard::KNewInvestmentWizard(const MyMoneySecurity& security, QWidget *parent) :
KNewInvestmentWizardDecl(parent),
m_security(security)
{
setWindowTitle(i18n("Security detail wizard"));
init1();
m_createAccount = false;
// load the widgets with the data
setName(security.name());
init2();
// no chance to change the price mode here
m_investmentDetailsPage->setCurrentPriceMode(0);
m_investmentDetailsPage->setPriceModeEnabled(false);
}
void KNewInvestmentWizard::init1()
{
m_onlineUpdatePage->slotSourceChanged(false);
// make sure, the back button does not clear fields
setOption(QWizard::IndependentPages, true);
// enable the help button
setOption(HaveHelpButton, true);
connect(this, SIGNAL(helpRequested()), this, SLOT(slotHelp()));
m_createAccount = true;
// Update label in case of edit
if (!m_account.id().isEmpty()) {
m_investmentTypePage->setIntroLabelText(i18n("This wizard allows you to modify the selected investment."));
}
if (!m_security.id().isEmpty()) {
m_investmentTypePage->setIntroLabelText(i18n("This wizard allows you to modify the selected security."));
}
KMyMoneyUtils::updateWizardButtons(this);
}
void KNewInvestmentWizard::init2()
{
m_investmentTypePage->init2(m_security);
m_investmentDetailsPage->init2(m_security);
m_onlineUpdatePage->init2(m_security);
m_onlineUpdatePage->slotCheckPage(m_security.value("kmm-online-source"));
}
KNewInvestmentWizard::~KNewInvestmentWizard()
{
}
void KNewInvestmentWizard::setName(const QString& name)
{
m_investmentDetailsPage->setName(name);
}
void KNewInvestmentWizard::slotCheckForExistingSymbol(const QString& symbol)
{
Q_UNUSED(symbol);
if (field("investmentName").toString().isEmpty()) {
QList<MyMoneySecurity> list = MyMoneyFile::instance()->securityList();
auto type = static_cast<eMyMoney::Security>(field("securityType").toInt());
foreach (const MyMoneySecurity& it_s, list) {
if (it_s.securityType() == type
&& it_s.tradingSymbol() == field("investmentSymbol").toString()) {
m_security = MyMoneySecurity();
if (KMessageBox::questionYesNo(this, i18n("The selected symbol is already on file. Do you want to reuse the existing security?"), i18n("Security found")) == KMessageBox::Yes) {
m_security = it_s;
init2();
m_investmentDetailsPage->loadName(m_security.name());
}
break;
}
}
}
}
void KNewInvestmentWizard::slotHelp()
{
KHelpClient::invokeHelp("details.investments.newinvestmentwizard");
}
void KNewInvestmentWizard::createObjects(const QString& parentId)
{
MyMoneyFile* file = MyMoneyFile::instance();
auto type = static_cast<eMyMoney::Security>(field("securityType").toInt());
auto roundingMethod = static_cast<AlkValue::RoundingMethod>(field("roundingMethod").toInt());
MyMoneyFileTransaction ft;
try {
// update all relevant attributes only, if we create a stock
// account and the security is unknown or we modifiy the security
MyMoneySecurity newSecurity(m_security);
newSecurity.setName(field("investmentName").toString());
newSecurity.setTradingSymbol(field("investmentSymbol").toString());
newSecurity.setTradingMarket(field("tradingMarket").toString());
newSecurity.setSmallestAccountFraction(field("fraction").value<MyMoneyMoney>().formatMoney("", 0, false).toUInt());
newSecurity.setPricePrecision(MyMoneyMoney(field("pricePrecision").toUInt()).formatMoney("", 0, false).toUInt());
newSecurity.setTradingCurrency(field("tradingCurrencyEdit").value<MyMoneySecurity>().id());
newSecurity.setSecurityType(type);
newSecurity.setRoundingMethod(roundingMethod);
newSecurity.deletePair("kmm-online-source");
newSecurity.deletePair("kmm-online-quote-system");
newSecurity.deletePair("kmm-online-factor");
newSecurity.deletePair("kmm-security-id");
if (!field("onlineSourceCombo").toString().isEmpty()) {
if (field("useFinanceQuote").toBool()) {
FinanceQuoteProcess p;
newSecurity.setValue("kmm-online-quote-system", "Finance::Quote");
newSecurity.setValue("kmm-online-source", p.crypticName(field("onlineSourceCombo").toString()));
} else {
newSecurity.setValue("kmm-online-source", field("onlineSourceCombo").toString());
}
}
if (m_onlineUpdatePage->isOnlineFactorEnabled() && (field("onlineFactor").value<MyMoneyMoney>() != MyMoneyMoney::ONE))
newSecurity.setValue("kmm-online-factor", field("onlineFactor").value<MyMoneyMoney>().toString());
if (!field("investmentIdentification").toString().isEmpty())
newSecurity.setValue("kmm-security-id", field("investmentIdentification").toString());
if (m_security.id().isEmpty() || newSecurity != m_security) {
m_security = newSecurity;
// add or update it
if (m_security.id().isEmpty()) {
file->addSecurity(m_security);
} else {
file->modifySecurity(m_security);
}
}
if (m_createAccount) {
// now that the security exists, we can add the account to store it
m_account.setName(field("investmentName").toString());
- if (m_account.accountType() == MyMoneyAccount::UnknownAccountType)
- m_account.setAccountType(MyMoneyAccount::Stock);
+ if (m_account.accountType() == eMyMoney::Account::Unknown)
+ m_account.setAccountType(eMyMoney::Account::Stock);
m_account.setCurrencyId(m_security.id());
switch (m_investmentDetailsPage->priceMode()) {
case 0:
m_account.deletePair("priceMode");
break;
case 1:
case 2:
m_account.setValue("priceMode", QString("%1").arg(m_investmentDetailsPage->priceMode()));
break;
}
// update account's fraction in case its security fraction has changed
// otherwise KMM restart is required because this won't happen automatically
m_account.fraction(m_security);
if (m_account.id().isEmpty()) {
MyMoneyAccount parent = file->account(parentId);
file->addAccount(m_account, parent);
} else
file->modifyAccount(m_account);
}
ft.commit();
} catch (const MyMoneyException &e) {
KMessageBox::detailedSorry(0, i18n("Unable to create all objects for the investment"), QString("%1 caugt in %2:%3").arg(e.what()).arg(e.file()).arg(e.line()));
}
}
diff --git a/kmymoney/wizards/newloanwizard/durationwizardpage.cpp b/kmymoney/wizards/newloanwizard/durationwizardpage.cpp
index d73d5a83a..ff7375cb5 100644
--- a/kmymoney/wizards/newloanwizard/durationwizardpage.cpp
+++ b/kmymoney/wizards/newloanwizard/durationwizardpage.cpp
@@ -1,119 +1,122 @@
/***************************************************************************
durationwizardpage - description
-------------------
begin : Sun Jul 4 2010
copyright : (C) 2010 by Fernando Vilas
email : kmymoney-devel@kde.org
***************************************************************************/
/***************************************************************************
* *
* 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 "durationwizardpage.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <qmath.h>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
+#include "mymoneyenums.h"
+
+using namespace eMyMoney;
DurationWizardPage::DurationWizardPage(QWidget *parent)
: DurationWizardPageDecl(parent)
{
- m_durationUnitEdit->insertItem(i18n("Months"), static_cast<int>(MyMoneySchedule::OCCUR_MONTHLY));
- m_durationUnitEdit->insertItem(i18n("Years"), static_cast<int>(MyMoneySchedule::OCCUR_YEARLY));
- m_durationUnitEdit->insertItem(i18n("Payments"), static_cast<int>(MyMoneySchedule::OCCUR_ONCE));
+ m_durationUnitEdit->insertItem(i18n("Months"), static_cast<int>(Schedule::Occurrence::Monthly));
+ m_durationUnitEdit->insertItem(i18n("Years"), static_cast<int>(Schedule::Occurrence::Yearly));
+ m_durationUnitEdit->insertItem(i18n("Payments"), static_cast<int>(Schedule::Occurrence::Once));
// Register the fields with the QWizard and connect the
// appropriate signals to update the "Next" button correctly
registerField("durationValueEdit", m_durationValueEdit, "value");
registerField("durationUnitEdit", m_durationUnitEdit, "currentText", SIGNAL(currentIndexChanged(QString)));
registerField("loanAmount3", m_loanAmount3, "text");
registerField("interestRate3", m_interestRate3, "text");
registerField("duration3", m_duration3, "text");
registerField("payment3", m_payment3, "text");
registerField("balloon3", m_balloon3, "text");
}
void DurationWizardPage::resetCalculator()
{
m_loanAmount3->setText(QString());
m_interestRate3->setText(QString());
m_duration3->setText(QString());
m_payment3->setText(QString());
m_balloon3->setText(QString());
}
int DurationWizardPage::term() const
{
int factor = 0;
if (m_durationValueEdit->value() != 0) {
factor = 1;
switch (m_durationUnitEdit->currentItem()) {
- case MyMoneySchedule::OCCUR_YEARLY: // years
+ case (int)Schedule::Occurrence::Yearly: // years
factor = 12;
// intentional fall through
- case MyMoneySchedule::OCCUR_MONTHLY: // months
+ case (int)Schedule::Occurrence::Monthly: // months
factor *= 30;
factor *= m_durationValueEdit->value();
// factor now is the duration in days. we divide this by the
// payment frequency and get the number of payments
- factor /= MyMoneySchedule::daysBetweenEvents(MyMoneySchedule::occurrenceE(field("paymentFrequencyUnitEdit").toInt()));
+ factor /= MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence(field("paymentFrequencyUnitEdit").toInt()));
break;
- case MyMoneySchedule::OCCUR_ONCE: // payments
+ case (int)Schedule::Occurrence::Once: // payments
factor = m_durationValueEdit->value();
break;
}
}
return factor;
}
QString DurationWizardPage::updateTermWidgets(const double val)
{
long vl = qFloor(val);
QString valString;
- MyMoneySchedule::occurrenceE unit;
- unit = MyMoneySchedule::occurrenceE(field("paymentFrequencyUnitEdit").toInt());
+ Schedule::Occurrence unit;
+ unit = Schedule::Occurrence(field("paymentFrequencyUnitEdit").toInt());
- if ((unit == MyMoneySchedule::OCCUR_MONTHLY)
+ if ((unit == Schedule::Occurrence::Monthly)
&& ((vl % 12) == 0)) {
vl /= 12;
- unit = MyMoneySchedule::OCCUR_YEARLY;
+ unit = Schedule::Occurrence::Yearly;
}
switch (unit) {
- case MyMoneySchedule::OCCUR_MONTHLY:
+ case Schedule::Occurrence::Monthly:
valString = i18np("one month", "%1 months", vl);
- m_durationUnitEdit->setCurrentItem(static_cast<int>(MyMoneySchedule::OCCUR_MONTHLY));
+ m_durationUnitEdit->setCurrentItem(static_cast<int>(Schedule::Occurrence::Monthly));
break;
- case MyMoneySchedule::OCCUR_YEARLY:
+ case Schedule::Occurrence::Yearly:
valString = i18np("one year", "%1 years", vl);
- m_durationUnitEdit->setCurrentItem(static_cast<int>(MyMoneySchedule::OCCUR_YEARLY));
+ m_durationUnitEdit->setCurrentItem(static_cast<int>(Schedule::Occurrence::Yearly));
break;
default:
valString = i18np("one payment", "%1 payments", vl);
- m_durationUnitEdit->setCurrentItem(static_cast<int>(MyMoneySchedule::OCCUR_ONCE));
+ m_durationUnitEdit->setCurrentItem(static_cast<int>(Schedule::Occurrence::Once));
break;
}
m_durationValueEdit->setValue(vl);
return valString;
}
diff --git a/kmymoney/wizards/newloanwizard/interestcategorywizardpage.cpp b/kmymoney/wizards/newloanwizard/interestcategorywizardpage.cpp
index 34d7cf90b..3118ce203 100644
--- a/kmymoney/wizards/newloanwizard/interestcategorywizardpage.cpp
+++ b/kmymoney/wizards/newloanwizard/interestcategorywizardpage.cpp
@@ -1,102 +1,102 @@
/***************************************************************************
interestcategorywizardpage - description
-------------------
begin : Sun Jul 4 2010
copyright : (C) 2010 by Fernando Vilas
email : kmymoney-devel@kde.org
***************************************************************************/
/***************************************************************************
* *
* 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 "interestcategorywizardpage.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QPointer>
#include <QIcon>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KMessageBox>
// ----------------------------------------------------------------------------
// Project Includes
#include "knewaccountdlg.h"
#include "mymoneyfile.h"
#include "icons/icons.h"
using namespace Icons;
InterestCategoryWizardPage::InterestCategoryWizardPage(QWidget *parent)
: InterestCategoryWizardPageDecl(parent)
{
// Register the fields with the QWizard and connect the
// appropriate signals to update the "Next" button correctly
registerField("interestAccountEdit", m_interestAccountEdit, "selectedItems");
connect(m_interestAccountEdit, SIGNAL(stateChanged()), this, SIGNAL(completeChanged()));
m_interestAccountEdit->removeButtons();
// load button icons
KGuiItem createCategoryButtonItem(i18n("&Create..."),
QIcon::fromTheme(g_Icons[Icon::DocumentNew]),
i18n("Create a new category"),
i18n("Use this to open the new account editor"));
KGuiItem::assign(m_createCategoryButton, createCategoryButtonItem);
connect(m_createCategoryButton, SIGNAL(clicked()), this, SLOT(slotCreateCategory()));
}
/**
* Update the "Next" button
*/
bool InterestCategoryWizardPage::isComplete() const
{
return m_interestAccountEdit->selectedItems().count() > 0;
}
void InterestCategoryWizardPage::slotCreateCategory()
{
MyMoneyAccount acc, base;
MyMoneyFile* file = MyMoneyFile::instance();
if (field("borrowButton").toBool()) {
base = file->expense();
- acc.setAccountType(MyMoneyAccount::Expense);
+ acc.setAccountType(eMyMoney::Account::Expense);
} else {
base = file->income();
- acc.setAccountType(MyMoneyAccount::Income);
+ acc.setAccountType(eMyMoney::Account::Income);
}
acc.setParentAccountId(base.id());
QPointer<KNewAccountDlg> dlg = new KNewAccountDlg(acc, true, true);
if (dlg->exec() == QDialog::Accepted) {
acc = dlg->account();
MyMoneyFileTransaction ft;
try {
QString id;
id = file->createCategory(base, acc.name());
if (id.isEmpty())
throw MYMONEYEXCEPTION("failure while creating the account hierarchy");
ft.commit();
m_interestAccountEdit->setSelected(id);
} catch (const MyMoneyException &e) {
KMessageBox::information(this, i18n("Unable to add account: %1", e.what()));
}
}
delete dlg;
}
diff --git a/kmymoney/wizards/newloanwizard/keditloanwizard.cpp b/kmymoney/wizards/newloanwizard/keditloanwizard.cpp
index e4d6147a7..fcb59869b 100644
--- a/kmymoney/wizards/newloanwizard/keditloanwizard.cpp
+++ b/kmymoney/wizards/newloanwizard/keditloanwizard.cpp
@@ -1,505 +1,506 @@
/***************************************************************************
keditloanwizard.cpp - description
-------------------
begin : Wed Nov 12 2003
copyright : (C) 2000-2003 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "keditloanwizard.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QButtonGroup>
#include <QRadioButton>
#include <QLabel>
#include <QList>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KMessageBox>
// ----------------------------------------------------------------------------
// Project Includes
#include "knewloanwizard.h"
#include "kmymoneylineedit.h"
#include "kmymoneyedit.h"
#include "kmymoneyaccountselector.h"
#include "mymoneyfile.h"
+#include "mymoneyinstitution.h"
KEditLoanWizard::KEditLoanWizard(const MyMoneyAccount& account, QWidget *parent) :
KNewLoanWizard(parent)
{
MyMoneyFile* file = MyMoneyFile::instance();
setWindowTitle(i18n("Edit loan wizard"));
m_account = account;
try {
QString id = m_account.value("schedule");
m_schedule = file->schedule(id);
} catch (const MyMoneyException &) {
}
m_lastSelection = -1;
loadWidgets(m_account);
if (m_account.openingDate() > QDate::currentDate()) {
//FIXME: port
m_effectiveDatePage->m_effectiveDateNoteLabel->setText(QString("\n") + i18n(
"Note: you will not be able to modify this account today, because the opening date \"%1\" is in the future. "
"Please revisit this dialog when the time has come.", QLocale().toString(m_account.openingDate())));
} else {
m_effectiveDatePage->m_effectiveDateNoteLabel->hide();
}
// turn off all pages that are contained here for derived classes
m_pages.clearBit(Page_Intro);
m_pages.clearBit(Page_NewGeneralInfo);
m_pages.clearBit(Page_LendBorrow);
m_pages.clearBit(Page_Name);
m_pages.clearBit(Page_NewCalculateLoan);
m_pages.clearBit(Page_NewPayments);
removePage(Page_AssetAccount);
m_assetAccountPage = 0;
// turn on all pages that are contained here for derived classes
m_pages.setBit(Page_EditIntro);
m_pages.setBit(Page_EditSelection);
// make sure, we show the correct start page
setStartId(Page_EditIntro);
}
KEditLoanWizard::~KEditLoanWizard()
{
}
void KEditLoanWizard::loadWidgets(const MyMoneyAccount& /* account */)
{
MyMoneyFile* file = MyMoneyFile::instance();
QString paymentAccountId, interestAccountId;
//FIXME: port
m_namePage->m_nameEdit->loadText(m_account.name());
m_loanAmountPage->m_loanAmountEdit->loadText(m_account.loanAmount().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))));
m_finalPaymentPage->m_finalPaymentEdit->loadText(m_account.finalPayment().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))));
setField("firstDueDateEdit", m_account.openingDate());
//FIXME: port
if (m_account.fixedInterestRate()) {
m_interestTypePage->m_fixedInterestButton->click();
} else {
m_interestTypePage->m_variableInterestButton->click();
}
QString institutionName = file->institution(m_account.institutionId()).name();
m_loanAttributesPage->setInstitution(institutionName);
MyMoneyMoney ir;
if (m_schedule.startDate() > QDate::currentDate()) {
ir = m_account.interestRate(m_schedule.startDate());
} else {
ir = m_account.interestRate(QDate::currentDate());
}
//FIXME: port
m_interestPage->m_interestRateEdit->loadText(ir.formatMoney("", 3));
m_interestPage->m_interestRateEdit->setPrecision(3);
m_interestEditPage->m_newInterestRateEdit->loadText(ir.formatMoney("", 3));
m_interestEditPage->m_newInterestRateEdit->setPrecision(3);
m_interestEditPage->m_interestRateLabel->setText(QString(" ") + ir.formatMoney("", 3) + QString("%"));
- m_paymentFrequencyPage->m_paymentFrequencyUnitEdit->setCurrentIndex(m_paymentFrequencyPage->m_paymentFrequencyUnitEdit->findData(QVariant(m_schedule.occurrencePeriod()), Qt::UserRole, Qt::MatchExactly));
+ m_paymentFrequencyPage->m_paymentFrequencyUnitEdit->setCurrentIndex(m_paymentFrequencyPage->m_paymentFrequencyUnitEdit->findData(QVariant((int)m_schedule.occurrencePeriod()), Qt::UserRole, Qt::MatchExactly));
m_durationPage->updateTermWidgets(m_account.term());
// the base payment (amortization and interest) is determined
// by adding all splits that are not automatically calculated.
// If the loan is a liability, we reverse the sign at the end
MyMoneyMoney basePayment;
MyMoneyMoney addPayment;
m_transaction = m_schedule.transaction();
foreach (const MyMoneySplit& it_s, m_schedule.transaction().splits()) {
MyMoneyAccount acc = file->account(it_s.accountId());
// if it's the split that references the source/dest
// of the money, we check if we borrow or loan money
if (paymentAccountId.isEmpty()
&& acc.isAssetLiability() && !acc.isLoan()
&& it_s.value() != MyMoneyMoney::autoCalc) {
if (it_s.value().isNegative()) {
setField("lendButton", false);
setField("borrowButton", true);
} else {
setField("lendButton", true);
setField("borrowButton", false);
}
// we keep the amount of the full payment and subtract the
// base payment later to get information about the additional payment
addPayment = it_s.value();
paymentAccountId = it_s.accountId();
MyMoneyPayee payee;
if (!it_s.payeeId().isEmpty()) {
try {
payee = file->payee(it_s.payeeId());
setField("payeeEdit", payee.id());
} catch (const MyMoneyException &) {
qWarning("Payee for schedule has been deleted");
}
}
// remove this split with one that will be replaced
// later and has a phony id
m_transaction.removeSplit(it_s);
m_split.clearId();
m_transaction.addSplit(m_split);
}
if (it_s.action() == MyMoneySplit::ActionInterest) {
interestAccountId = it_s.accountId();
}
if (it_s.value() != MyMoneyMoney::autoCalc) {
basePayment += it_s.value();
} else {
// remove the splits which should not show up
// for additional fees
m_transaction.removeSplit(it_s);
}
}
if (field("borrowButton").toBool()) {
basePayment = -basePayment;
addPayment = -addPayment;
}
// now make adjustment to get the amount of the additional fees
addPayment -= basePayment;
// load account selection widgets now that we know if
// we borrow or lend money
loadAccountList();
int fraction = m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()));
//FIXME: port
m_paymentPage->m_paymentEdit->loadText(basePayment.formatMoney(fraction));
m_paymentEditPage->m_newPaymentEdit->loadText(basePayment.formatMoney(fraction));
m_paymentEditPage->m_paymentLabel->setText(QString(" ") + basePayment.formatMoney(fraction));
setField("additionalCost", addPayment.formatMoney(fraction));
m_interestCategoryPage->m_interestAccountEdit->setSelected(interestAccountId);
m_schedulePage->m_paymentAccountEdit->setSelected(paymentAccountId);
setField("nextDueDateEdit", m_schedule.nextPayment());
int changeFrequencyUnit;
int amt = m_account.interestChangeFrequency(&changeFrequencyUnit);
if (amt != -1) {
setField("interestFrequencyAmountEdit", amt);
setField("interestFrequencyUnitEdit", changeFrequencyUnit);
}
// keep track, if the loan should be fully repayed
m_fullyRepayLoan = m_account.finalPayment() < basePayment;
updateLoanInfo();
}
bool KEditLoanWizard::validateCurrentPage()
{
bool dontLeavePage = false;
//FIXME: port m_lastSelection
QAbstractButton* button = m_editSelectionPage->m_selectionButtonGroup->button(m_lastSelection);
if (currentPage() == m_editSelectionPage) {
if (button != 0
&& m_lastSelection != m_editSelectionPage->m_selectionButtonGroup->checkedId()) {
QString errMsg = i18n(
"Your previous selection was \"%1\". If you select another option, "
"KMyMoney will dismiss the changes you have just entered. "
"Do you wish to proceed?", button->text());
if (KMessageBox::questionYesNo(this, errMsg) == KMessageBox::No) {
dontLeavePage = true;
} else {
loadWidgets(m_account);
}
}
if (!dontLeavePage) {
// turn off all pages except the summary at the end
// and the one's we need for the selected option
// and load the widgets with the current values
// general info
m_pages.clearBit(Page_Name);
m_pages.clearBit(Page_InterestType);
m_pages.clearBit(Page_PreviousPayments);
m_pages.clearBit(Page_RecordPayment);
m_pages.clearBit(Page_VariableInterestDate);
m_pages.clearBit(Page_FirstPayment);
// loan calculation
m_pages.clearBit(Page_PaymentEdit);
m_pages.clearBit(Page_InterestEdit);
m_pages.clearBit(Page_PaymentFrequency);
m_pages.clearBit(Page_InterestCalculation);
m_pages.clearBit(Page_LoanAmount);
m_pages.clearBit(Page_Interest);
m_pages.clearBit(Page_Duration);
m_pages.clearBit(Page_Payment);
m_pages.clearBit(Page_FinalPayment);
m_pages.clearBit(Page_CalculationOverview);
// payment
m_pages.clearBit(Page_InterestCategory);
m_pages.clearBit(Page_AdditionalFees);
m_pages.clearBit(Page_Schedule);
m_pages.setBit(Page_Summary);
// Attributes
m_pages.clearBit(Page_LoanAttributes);
m_pages.setBit(Page_EffectiveDate);
if (page(Page_Summary) != 0) {
removePage(Page_Summary);
}
if (field("editInterestRateButton").toBool()) {
m_pages.setBit(Page_PaymentFrequency);
m_pages.setBit(Page_InterestType);
m_pages.setBit(Page_VariableInterestDate);
m_pages.setBit(Page_PaymentEdit);
m_pages.setBit(Page_InterestEdit);
m_pages.setBit(Page_InterestCategory);
m_pages.setBit(Page_Schedule);
m_pages.setBit(Page_SummaryEdit);
} else if (field("editOtherCostButton").toBool()) {
m_pages.setBit(Page_PaymentFrequency);
m_pages.setBit(Page_AdditionalFees);
m_pages.setBit(Page_InterestCategory);
m_pages.setBit(Page_Schedule);
m_pages.setBit(Page_SummaryEdit);
} else if (field("editOtherInfoButton").toBool()) {
m_pages.setBit(Page_Name);
m_pages.setBit(Page_InterestCalculation);
m_pages.setBit(Page_Interest);
m_pages.setBit(Page_Duration);
m_pages.setBit(Page_Payment);
m_pages.setBit(Page_FinalPayment);
m_pages.setBit(Page_CalculationOverview);
m_pages.setBit(Page_InterestCategory);
m_pages.setBit(Page_AdditionalFees);
m_pages.setBit(Page_Schedule);
m_pages.clearBit(Page_SummaryEdit);
setPage(Page_Summary, m_summaryPage);
m_pages.setBit(Page_Summary);
} else if (field("editAttributesButton").toBool()) {
m_pages.setBit(Page_LoanAttributes);
m_pages.clearBit(Page_EffectiveDate);
} else {
qWarning("%s,%d: This should never happen", __FILE__, __LINE__);
}
m_lastSelection = m_editSelectionPage->m_selectionButtonGroup->checkedId();
} // if(!dontLeavePage)
} else if (currentPage() == m_additionalFeesPage) {
if (field("editOtherCostButton").toBool()) {
updateLoanInfo();
updateEditSummary();
}
} else if (currentPage() == m_interestEditPage) {
// copy the necessary data to the widgets used for calculation
//FIXME: port to fields
m_interestPage->m_interestRateEdit->setValue(field("newInterestRateEdit").value<MyMoneyMoney>());
m_paymentPage->m_paymentEdit->setValue(field("newPaymentEdit").value<MyMoneyMoney>());
// if interest rate and payment amount is given, then force
// the term to be recalculated. The final payment is adjusted to
// 0 if the loan was ment to be fully repayed
m_durationPage->updateTermWidgets(m_account.term());
if (field("interestRateEditValid").toBool()
&& field("paymentEditValid").toBool()) {
// if there's an amortization going on, we can evaluate
// the new term. If the amortization is 0 (interest only
// payments) then we keep the term as entered by the user.
if (field("loanAmountEdit").value<MyMoneyMoney>() != field("finalPaymentEdit").value<MyMoneyMoney>()) {
setField("durationValueEdit", 0);
}
if (m_fullyRepayLoan)
m_finalPaymentPage->m_finalPaymentEdit->loadText(MyMoneyMoney().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))));
}
/*
// we need to calculate the balance at the time of the change
// in order to accurately recalculate the term. A special
// situation arises, when we keep track of all payments and
// the full loan is not yet paid out. In this case, we take the
// the loan amount minus all amortization payments as the current
// balance.
// FIXME: This needs some more thoughts. We leave it out for
// now and always calculate with the full loan amount.
MyMoneyMoney balance = m_account.openingBalance();
QList<MyMoneyTransaction> list;
QList<MyMoneyTransaction>::ConstIterator it;
MyMoneySplit split;
MyMoneyTransactionFilter filter(m_account.id());
filter.setDateFilter(QDate(), m_effectiveChangeDateEdit->date().addDays(-1));
list = MyMoneyFile::instance()->transactionList(filter);
for(it = list.begin(); it != list.end(); ++it) {
try {
split = (*it).splitByAccount(m_account.id());
balance += split.value();
} catch(const MyMoneyException &e) {
// account is not referenced within this transaction
}
}
m_loanAmountEdit->setText(balance.formatMoney());
*/
// now re-calculate the figures
dontLeavePage = !calculateLoan();
// reset the original loan amount to the widget
//FIXME: port to fields
m_loanAmountPage->m_loanAmountEdit->setValue(m_account.loanAmount());
if (!dontLeavePage) {
updateLoanInfo();
updateEditSummary();
}
}
if (!dontLeavePage)
dontLeavePage = ! KNewLoanWizard::validateCurrentPage();
// These might have been set by KNewLoanWizard
m_pages.clearBit(Page_PreviousPayments);
m_pages.clearBit(Page_RecordPayment);
if (dontLeavePage)
return false;
// we never need to show this page
if (currentPage() == m_previousPaymentsPage)
dontLeavePage = KNewLoanWizard::validateCurrentPage();
return ! dontLeavePage;
}
void KEditLoanWizard::updateEditSummary()
{
// calculate the number of affected transactions
MyMoneyTransactionFilter filter(m_account.id());
filter.setDateFilter(field("effectiveChangeDateEdit").toDate(), QDate());
int count = 0;
QList<MyMoneyTransaction> list;
list = MyMoneyFile::instance()->transactionList(filter);
foreach (const MyMoneyTransaction& it, list) {
int match = 0;
foreach (const MyMoneySplit& it_s, it.splits()) {
// we only count those transactions that have an interest
// and amortization part
if (it_s.action() == MyMoneySplit::ActionInterest)
match |= 0x01;
if (it_s.action() == MyMoneySplit::ActionAmortization)
match |= 0x02;
}
if (match == 0x03)
++count;
}
setField("affectedPayments", QString().sprintf("%d", count));
}
const MyMoneySchedule KEditLoanWizard::schedule() const
{
MyMoneySchedule sched = m_schedule;
sched.setTransaction(transaction());
- sched.setOccurrence(MyMoneySchedule::occurrenceE(field("paymentFrequencyUnitEdit").toInt()));
+ sched.setOccurrence(eMyMoney::Schedule::Occurrence(field("paymentFrequencyUnitEdit").toInt()));
if (field("nextDueDateEdit").toDate() < m_schedule.startDate())
sched.setStartDate(field("nextDueDateEdit").toDate());
return sched;
}
const MyMoneyAccount KEditLoanWizard::account() const
{
MyMoneyAccountLoan acc(m_account);
if (field("interestOnReceptionButton").toBool())
acc.setInterestCalculation(MyMoneyAccountLoan::paymentReceived);
else
acc.setInterestCalculation(MyMoneyAccountLoan::paymentDue);
MyMoneyFile *file = MyMoneyFile::instance();
QString institution = m_loanAttributesPage->m_qcomboboxInstitutions->currentText();
if (institution != i18n("(No Institution)")) {
QList<MyMoneyInstitution> list;
file->institutionList(list);
Q_FOREACH(const MyMoneyInstitution& testInstitution, list) {
if (testInstitution.name() == institution) {
acc.setInstitutionId(testInstitution.id());
break;
}
}
} else {
acc.setInstitutionId(QString());
}
acc.setFixedInterestRate(field("fixedInterestButton").toBool());
acc.setFinalPayment(field("finalPaymentEdit").value<MyMoneyMoney>());
acc.setTerm(m_durationPage->term());
acc.setPeriodicPayment(field("paymentEdit").value<MyMoneyMoney>());
acc.setInterestRate(field("effectiveChangeDateEdit").toDate(), field("interestRateEdit").value<MyMoneyMoney>());
acc.setPayee(field("payeeEdit").toString());
if (field("variableInterestButton").toBool()) {
acc.setNextInterestChange(field("interestChangeDateEdit").toDate());
acc.setInterestChangeFrequency(field("interestFrequencyAmountEdit").toInt(),
field("interestFrequencyUnitEdit").toInt());
}
return acc;
}
const MyMoneyTransaction KEditLoanWizard::transaction() const
{
MyMoneyTransaction t = KNewLoanWizard::transaction();
MyMoneySplit s = t.splitByAccount(QString("Phony-ID"));
s.setAccountId(m_account.id());
t.modifySplit(s);
return t;
}
diff --git a/kmymoney/wizards/newloanwizard/knewloanwizard.cpp b/kmymoney/wizards/newloanwizard/knewloanwizard.cpp
index 65eb507a2..8a69d65ae 100644
--- a/kmymoney/wizards/newloanwizard/knewloanwizard.cpp
+++ b/kmymoney/wizards/newloanwizard/knewloanwizard.cpp
@@ -1,656 +1,656 @@
/***************************************************************************
knewloanwizard.cpp - description
-------------------
begin : Wed Oct 8 2003
copyright : (C) 2000-2003 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@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 "knewloanwizard.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QList>
#include <qmath.h>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KMessageBox>
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneyutils.h"
#include "kmymoneydateinput.h"
#include "kmymoneyedit.h"
#include "kmymoneyaccountselector.h"
#include "kmymoneysettings.h"
#include "mymoneyfinancialcalculator.h"
#include "mymoneyfile.h"
KNewLoanWizard::KNewLoanWizard(QWidget *parent) :
KNewLoanWizardDecl(parent), m_pages(Page_Summary + 1, true)
{
setModal(true);
KMyMoneyMVCCombo::setSubstringSearchForChildren(m_namePage, !KMyMoneySettings::stringMatchFromStart());
// make sure, the back button does not clear fields
setOption(QWizard::IndependentPages, true);
// connect(m_payeeEdit, SIGNAL(newPayee(QString)), this, SLOT(slotNewPayee(QString)));
connect(m_namePage->m_payeeEdit, SIGNAL(createItem(QString,QString&)), this, SIGNAL(createPayee(QString,QString&)));
connect(m_additionalFeesPage, SIGNAL(newCategory(MyMoneyAccount&)), this, SIGNAL(newCategory(MyMoneyAccount&)));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotReloadEditWidgets()));
resetCalculator();
slotReloadEditWidgets();
// As default we assume a liability loan, with fixed interest rate,
// with a first payment due on the 30th of this month. All payments
// should be recorded and none have been made so far.
//FIXME: port
m_firstPaymentPage->m_firstDueDateEdit->loadDate(QDate(QDate::currentDate().year(), QDate::currentDate().month(), 30));
// FIXME: we currently only support interest calculation on reception
m_pages.clearBit(Page_InterestCalculation);
// turn off all pages that are contained here for derived classes
m_pages.clearBit(Page_EditIntro);
m_pages.clearBit(Page_EditSelection);
m_pages.clearBit(Page_EffectiveDate);
m_pages.clearBit(Page_PaymentEdit);
m_pages.clearBit(Page_InterestEdit);
m_pages.clearBit(Page_SummaryEdit);
// for now, we don't have online help :-(
setOption(QWizard::HaveHelpButton, false);
// setup a phony transaction for additional fee processing
m_account = MyMoneyAccount("Phony-ID", MyMoneyAccount());
m_split.setAccountId(m_account.id());
m_split.setValue(MyMoneyMoney());
m_transaction.addSplit(m_split);
KMyMoneyUtils::updateWizardButtons(this);
}
KNewLoanWizard::~KNewLoanWizard()
{
}
const MyMoneyAccountLoan KNewLoanWizard::account() const
{
return m_account;
}
int KNewLoanWizard::nextId() const
{
// Starting from the current page, look for the first enabled page
// and return that value
// If the end of the list is encountered first, then return -1.
for (int i = currentId() + 1; i < m_pages.size() && i < pageIds().size(); ++i) {
if (m_pages.testBit(i))
return pageIds()[i];
}
return -1;
}
void KNewLoanWizard::resetCalculator()
{
m_loanAmountPage->resetCalculator();
m_interestPage->resetCalculator();
m_durationPage->resetCalculator();
m_paymentPage->resetCalculator();
m_finalPaymentPage->resetCalculator();
setField("additionalCost", MyMoneyMoney().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))));
}
void KNewLoanWizard::updateLoanAmount()
{
QString txt;
//FIXME: port
if (! field("loanAmountEditValid").toBool()) {
txt = QString("<") + i18n("calculate") + QString(">");
} else {
txt = field("loanAmountEdit").value<MyMoneyMoney>().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())));
}
setField("loanAmount1", txt);
setField("loanAmount2", txt);
setField("loanAmount3", txt);
setField("loanAmount4", txt);
setField("loanAmount5", txt);
}
void KNewLoanWizard::updateInterestRate()
{
QString txt;
//FIXME: port
if (! field("interestRateEditValid").toBool()) {
txt = QString("<") + i18n("calculate") + QString(">");
} else {
txt = field("interestRateEdit").value<MyMoneyMoney>().formatMoney("", 3) + QString("%");
}
setField("interestRate1", txt);
setField("interestRate2", txt);
setField("interestRate3", txt);
setField("interestRate4", txt);
setField("interestRate5", txt);
}
void KNewLoanWizard::updateDuration()
{
QString txt;
//FIXME: port
if (field("durationValueEdit").toInt() == 0) {
txt = QString("<") + i18n("calculate") + QString(">");
} else {
txt = QString().sprintf("%d ", field("durationValueEdit").toInt())
+ field("durationUnitEdit").toString();
}
setField("duration1", txt);
setField("duration2", txt);
setField("duration3", txt);
setField("duration4", txt);
setField("duration5", txt);
}
void KNewLoanWizard::updatePayment()
{
QString txt;
//FIXME: port
if (! field("paymentEditValid").toBool()) {
txt = QString("<") + i18n("calculate") + QString(">");
} else {
txt = field("paymentEdit").value<MyMoneyMoney>().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())));
}
setField("payment1", txt);
setField("payment2", txt);
setField("payment3", txt);
setField("payment4", txt);
setField("payment5", txt);
setField("basePayment", txt);
}
void KNewLoanWizard::updateFinalPayment()
{
QString txt;
//FIXME: port
if (! field("finalPaymentEditValid").toBool()) {
txt = QString("<") + i18n("calculate") + QString(">");
} else {
txt = field("finalPaymentEdit").value<MyMoneyMoney>().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())));
}
setField("balloon1", txt);
setField("balloon2", txt);
setField("balloon3", txt);
setField("balloon4", txt);
setField("balloon5", txt);
}
void KNewLoanWizard::updateLoanInfo()
{
updateLoanAmount();
updateInterestRate();
updateDuration();
updatePayment();
updateFinalPayment();
m_additionalFeesPage->updatePeriodicPayment(m_account);
QString txt;
int fraction = m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()));
setField("loanAmount6", field("loanAmountEdit").value<MyMoneyMoney>().formatMoney(fraction));
setField("interestRate6", QString(field("interestRateEdit").value<MyMoneyMoney>().formatMoney("", 3) + QString("%")));
txt = QString().sprintf("%d ", field("durationValueEdit").toInt())
+ field("durationUnitEdit").toString();
setField("duration6", txt);
setField("payment6", field("paymentEdit").value<MyMoneyMoney>().formatMoney(fraction));
setField("balloon6", field("finalPaymentEdit").value<MyMoneyMoney>().formatMoney(fraction));
}
bool KNewLoanWizard::validateCurrentPage()
{
bool dontLeavePage = false;
KLocalizedString ks = ki18n(
"The loan wizard is unable to calculate two different values for your loan "
"at the same time. "
"Please enter a value for the %1 on this page or backup to the page where the "
"current value to be calculated is defined and fill in a value.");
if (currentPage() == m_lendBorrowPage) {
// load the appropriate categories into the list
loadAccountList();
} else if (currentPage() == m_interestTypePage) {
if (field("fixedInterestButton").toBool()) {
m_pages.setBit(Page_PreviousPayments);
if (field("previousPaymentButton").toBool())
m_pages.setBit(Page_RecordPayment);
else
m_pages.clearBit(Page_RecordPayment);
m_pages.clearBit(Page_VariableInterestDate);
} else {
m_pages.clearBit(Page_PreviousPayments);
m_pages.clearBit(Page_RecordPayment);
m_pages.setBit(Page_VariableInterestDate);
}
} else if (currentPage() == m_previousPaymentsPage) {
if (field("previousPaymentButton").toBool()) {
m_pages.setBit(Page_RecordPayment);
} else if (field("noPreviousPaymentButton").toBool()) {
m_pages.clearBit(Page_RecordPayment);
}
} else if (currentPage() == m_loanAmountPage) {
if (field("thisYearPaymentButton").toBool()
&& !field("loanAmountEditValid").toBool()) {
dontLeavePage = true;
KMessageBox::error(0, i18n("You selected, that payments have already been made towards this loan. "
"This requires you to enter the loan amount exactly as found on your "
"last statement."), i18n("Calculation error"));
} else
updateLoanAmount();
} else if (currentPage() == m_interestPage) {
if (!field("loanAmountEditValid").toBool()
&& !field("interestRateEditValid").toBool()) {
dontLeavePage = true;
KMessageBox::error(0, ks.subs(i18n("interest rate")).toString(), i18n("Calculation error"));
} else
updateInterestRate();
} else if (currentPage() == m_durationPage) {
if ((!field("loanAmountEditValid").toBool()
|| !field("interestRateEditValid").toBool())
&& field("durationValueEdit").toInt() == 0) {
dontLeavePage = true;
KMessageBox::error(0, ks.subs(i18n("term")).toString(), i18n("Calculation error"));
} else
updateDuration();
} else if (currentPage() == m_paymentPage) {
if ((!field("loanAmountEditValid").toBool()
|| !field("interestRateEditValid").toBool()
|| field("durationValueEdit").toInt() == 0)
&& !field("paymentEditValid").toBool()) {
dontLeavePage = true;
KMessageBox::error(0, ks.subs(i18n("principal and interest")).toString(), i18n("Calculation error"));
} else
updatePayment();
} else if (currentPage() == m_finalPaymentPage) {
if ((!field("loanAmountEditValid").toBool()
|| !field("interestRateEditValid").toBool()
|| field("durationValueEdit").toInt() == 0
|| !field("paymentEditValid").toBool())
&& !field("finalPaymentEditValid").toBool()) {
// if two fields are empty and one of them is the final payment
// we assume the final payment to be 0 instead of presenting a dialog
setField("finalPaymentEdit", QVariant::fromValue<MyMoneyMoney>((MyMoneyMoney())));
}
updateFinalPayment();
if (!calculateLoan()) {
dontLeavePage = true;
} else
updateLoanInfo();
} else if (currentPage() == m_schedulePage) {
if (field("allPaymentsButton").toBool() || field("noPreviousPaymentButton").toBool()) {
if (m_assetAccountPage)
m_pages.setBit(Page_AssetAccount);
} else {
if (m_assetAccountPage)
m_pages.clearBit(Page_AssetAccount);
m_assetAccountPage->m_assetAccountEdit->slotDeselectAllAccounts();
}
}
if (!dontLeavePage)
return KNewLoanWizardDecl::validateCurrentPage();
else
return false;
}
int KNewLoanWizard::calculateLoan()
{
MyMoneyFinancialCalculator calc;
double val;
int PF;
QString result;
// FIXME: for now, we only support interest calculation at the end of the period
calc.setBep();
// FIXME: for now, we only support periodic compounding
calc.setDisc();
- PF = MyMoneySchedule::eventsPerYear(MyMoneySchedule::occurrenceE(field("paymentFrequencyUnitEdit").toInt()));
+ PF = MyMoneySchedule::eventsPerYear(eMyMoney::Schedule::Occurrence(field("paymentFrequencyUnitEdit").toInt()));
if (PF == 0)
return 0;
calc.setPF(PF);
// FIXME: for now we only support compounding frequency == payment frequency
calc.setCF(PF);
if (field("loanAmountEditValid").toBool()) {
val = field("loanAmountEdit").value<MyMoneyMoney>().abs().toDouble();
if (field("borrowButton").toBool())
val = -val;
calc.setPv(val);
}
if (field("interestRateEditValid").toBool()) {
val = field("interestRateEdit").value<MyMoneyMoney>().abs().toDouble();
calc.setIr(val);
}
if (field("paymentEditValid").toBool()) {
val = field("paymentEdit").value<MyMoneyMoney>().abs().toDouble();
if (field("lendButton").toBool())
val = -val;
calc.setPmt(val);
}
if (field("finalPaymentEditValid").toBool()) {
val = field("finalPaymentEditValid").value<MyMoneyMoney>().abs().toDouble();
if (field("lendButton").toBool())
val = -val;
calc.setFv(val);
}
if (field("durationValueEdit").toInt() != 0) {
calc.setNpp(m_durationPage->term());
}
int fraction = m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()));
// setup of parameters is done, now do the calculation
try {
//FIXME: port
if (!field("loanAmountEditValid").toBool()) {
// calculate the amount of the loan out of the other information
val = calc.presentValue();
m_loanAmountPage->m_loanAmountEdit->loadText(MyMoneyMoney(static_cast<double>(val)).abs().formatMoney(fraction));
result = i18n("KMyMoney has calculated the amount of the loan as %1.", m_loanAmountPage->m_loanAmountEdit->lineedit()->text());
} else if (!field("interestRateEditValid").toBool()) {
// calculate the interest rate out of the other information
val = calc.interestRate();
m_interestPage->m_interestRateEdit->loadText(MyMoneyMoney(static_cast<double>(val)).abs().formatMoney("", 3));
result = i18n("KMyMoney has calculated the interest rate to %1%.", m_interestPage->m_interestRateEdit->lineedit()->text());
} else if (!field("paymentEditValid").toBool()) {
// calculate the periodical amount of the payment out of the other information
val = calc.payment();
setField("paymentEdit", QVariant::fromValue<MyMoneyMoney>(MyMoneyMoney(val).abs()));
// reset payment as it might have changed due to rounding
val = field("paymentEdit").value<MyMoneyMoney>().abs().toDouble();
if (field("lendButton").toBool())
val = -val;
calc.setPmt(val);
result = i18n("KMyMoney has calculated a periodic payment of %1 to cover principal and interest.", m_paymentPage->m_paymentEdit->lineedit()->text());
val = calc.futureValue();
if ((field("borrowButton").toBool() && val < 0 && qAbs(val) >= qAbs(calc.payment()))
|| (field("lendButton").toBool() && val > 0 && qAbs(val) >= qAbs(calc.payment()))) {
calc.setNpp(calc.npp() - 1);
m_durationPage->updateTermWidgets(calc.npp());
val = calc.futureValue();
MyMoneyMoney refVal(static_cast<double>(val));
m_finalPaymentPage->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction));
result += QString(" ");
result += i18n("The number of payments has been decremented and the final payment has been modified to %1.", m_finalPaymentPage->m_finalPaymentEdit->lineedit()->text());
} else if ((field("borrowButton").toBool() && val < 0 && qAbs(val) < qAbs(calc.payment()))
|| (field("lendButton").toBool() && val > 0 && qAbs(val) < qAbs(calc.payment()))) {
m_finalPaymentPage->m_finalPaymentEdit->loadText(MyMoneyMoney().formatMoney(fraction));
} else {
MyMoneyMoney refVal(static_cast<double>(val));
m_finalPaymentPage->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction));
result += i18n("The final payment has been modified to %1.", m_finalPaymentPage->m_finalPaymentEdit->lineedit()->text());
}
} else if (field("durationValueEdit").toInt() == 0) {
// calculate the number of payments out of the other information
val = calc.numPayments();
if (val == 0)
throw MYMONEYEXCEPTION("incorrect fincancial calculation");
// if the number of payments has a fractional part, then we
// round it to the smallest integer and calculate the balloon payment
result = i18n("KMyMoney has calculated the term of your loan as %1. ", m_durationPage->updateTermWidgets(qFloor(val)));
if (val != qFloor(val)) {
calc.setNpp(qFloor(val));
val = calc.futureValue();
MyMoneyMoney refVal(static_cast<double>(val));
m_finalPaymentPage->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction));
result += i18n("The final payment has been modified to %1.", m_finalPaymentPage->m_finalPaymentEdit->lineedit()->text());
}
} else {
// calculate the future value of the loan out of the other information
val = calc.futureValue();
// we differentiate between the following cases:
// a) the future value is greater than a payment
// b) the future value is less than a payment or the loan is overpaid
// c) all other cases
//
// a) means, we have paid more than we owed. This can't be
// b) means, we paid more than we owed but the last payment is
// less in value than regular payments. That means, that the
// future value is to be treated as (fully payed back)
// c) the loan is not payed back yet
if ((field("borrowButton").toBool() && val < 0 && qAbs(val) > qAbs(calc.payment()))
|| (field("lendButton").toBool() && val > 0 && qAbs(val) > qAbs(calc.payment()))) {
// case a)
qDebug("Future Value is %f", val);
throw MYMONEYEXCEPTION("incorrect fincancial calculation");
} else if ((field("borrowButton").toBool() && val < 0 && qAbs(val) <= qAbs(calc.payment()))
|| (field("lendButton").toBool() && val > 0 && qAbs(val) <= qAbs(calc.payment()))) {
// case b)
val = 0;
}
MyMoneyMoney refVal(static_cast<double>(val));
result = i18n("KMyMoney has calculated a final payment of %1 for this loan.", refVal.abs().formatMoney(fraction));
if (field("finalPaymentEditValid").toBool()) {
if ((field("finalPaymentEdit").value<MyMoneyMoney>().abs() - refVal.abs()).abs().toDouble() > 1) {
throw MYMONEYEXCEPTION("incorrect fincancial calculation");
}
result = i18n("KMyMoney has successfully verified your loan information.");
}
//FIXME: port
m_finalPaymentPage->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction));
}
} catch (const MyMoneyException &) {
KMessageBox::error(0,
i18n("You have entered mis-matching information. Please backup to the "
"appropriate page and update your figures or leave one value empty "
"to let KMyMoney calculate it for you"),
i18n("Calculation error"));
return 0;
}
result += i18n("\n\nAccept this or modify the loan information and recalculate.");
KMessageBox::information(0, result, i18n("Calculation successful"));
return 1;
}
void KNewLoanWizard::loadAccountList()
{
AccountSet interestSet, assetSet;
if (field("borrowButton").toBool()) {
- interestSet.addAccountType(MyMoneyAccount::Expense);
+ interestSet.addAccountType(eMyMoney::Account::Expense);
} else {
- interestSet.addAccountType(MyMoneyAccount::Income);
+ interestSet.addAccountType(eMyMoney::Account::Income);
}
if (m_interestCategoryPage)
interestSet.load(m_interestCategoryPage->m_interestAccountEdit);
- assetSet.addAccountType(MyMoneyAccount::Checkings);
- assetSet.addAccountType(MyMoneyAccount::Savings);
- assetSet.addAccountType(MyMoneyAccount::Cash);
- assetSet.addAccountType(MyMoneyAccount::Asset);
- assetSet.addAccountType(MyMoneyAccount::Currency);
+ assetSet.addAccountType(eMyMoney::Account::Checkings);
+ assetSet.addAccountType(eMyMoney::Account::Savings);
+ assetSet.addAccountType(eMyMoney::Account::Cash);
+ assetSet.addAccountType(eMyMoney::Account::Asset);
+ assetSet.addAccountType(eMyMoney::Account::Currency);
if (m_assetAccountPage)
assetSet.load(m_assetAccountPage->m_assetAccountEdit);
- assetSet.addAccountType(MyMoneyAccount::CreditCard);
- assetSet.addAccountType(MyMoneyAccount::Liability);
+ assetSet.addAccountType(eMyMoney::Account::CreditCard);
+ assetSet.addAccountType(eMyMoney::Account::Liability);
if (m_schedulePage)
assetSet.load(m_schedulePage->m_paymentAccountEdit);
}
MyMoneyTransaction KNewLoanWizard::transaction() const
{
MyMoneyTransaction t;
bool hasInterest = !field("interestRateEdit").value<MyMoneyMoney>().isZero();
MyMoneySplit sPayment, sInterest, sAmortization;
// setup accounts. at this point, we cannot fill in the id of the
// account that the amortization will be performed on, because we
// create the account. So the id is yet unknown.
sPayment.setAccountId(field("paymentAccountEdit").toStringList().first());
//Only create the interest split if not zero
if (hasInterest) {
sInterest.setAccountId(field("interestAccountEdit").toStringList().first());
sInterest.setValue(MyMoneyMoney::autoCalc);
sInterest.setShares(sInterest.value());
sInterest.setAction(MyMoneySplit::ActionInterest);
}
// values
if (field("borrowButton").toBool()) {
sPayment.setValue(-field("paymentEdit").value<MyMoneyMoney>());
} else {
sPayment.setValue(field("paymentEdit").value<MyMoneyMoney>());
}
sAmortization.setValue(MyMoneyMoney::autoCalc);
// don't forget the shares
sPayment.setShares(sPayment.value());
sAmortization.setShares(sAmortization.value());
// setup the commodity
MyMoneyAccount acc = MyMoneyFile::instance()->account(sPayment.accountId());
t.setCommodity(acc.currencyId());
// actions
sPayment.setAction(MyMoneySplit::ActionAmortization);
sAmortization.setAction(MyMoneySplit::ActionAmortization);
// payee
QString payeeId = field("payeeEdit").toString();
sPayment.setPayeeId(payeeId);
sAmortization.setPayeeId(payeeId);
MyMoneyAccount account("Phony-ID", MyMoneyAccount());
sAmortization.setAccountId(account.id());
// IMPORTANT: Payment split must be the first one, because
// the schedule view expects it this way during display
t.addSplit(sPayment);
t.addSplit(sAmortization);
if (hasInterest) {
t.addSplit(sInterest);
}
// copy the splits from the other costs and update the payment split
foreach (const MyMoneySplit& it, m_transaction.splits()) {
if (it.accountId() != account.id()) {
MyMoneySplit sp = it;
sp.clearId();
t.addSplit(sp);
sPayment.setValue(sPayment.value() - sp.value());
sPayment.setShares(sPayment.value());
t.modifySplit(sPayment);
}
}
return t;
}
MyMoneySchedule KNewLoanWizard::schedule() const
{
MyMoneySchedule sched(field("nameEdit").toString(),
- MyMoneySchedule::TYPE_LOANPAYMENT,
- MyMoneySchedule::occurrenceE(field("paymentFrequencyUnitEdit").toInt()), 1,
- MyMoneySchedule::STYPE_OTHER,
+ eMyMoney::Schedule::Type::LoanPayment,
+ eMyMoney::Schedule::Occurrence(field("paymentFrequencyUnitEdit").toInt()), 1,
+ eMyMoney::Schedule::PaymentType::Other,
QDate(),
QDate(),
false,
false);
MyMoneyTransaction t = transaction();
t.setPostDate(field("nextDueDateEdit").toDate());
sched.setTransaction(t);
return sched;
}
void KNewLoanWizard::slotReloadEditWidgets()
{
// load the various account widgets
loadAccountList();
// reload payee widget
QString payeeId = field("payeeEdit").toString();
//FIXME: port
m_namePage->m_payeeEdit->loadPayees(MyMoneyFile::instance()->payeeList());
if (!payeeId.isEmpty()) {
setField("payeeEdit", payeeId);
}
}
QString KNewLoanWizard::initialPaymentAccount() const
{
if (field("dontCreatePayoutCheckBox").toBool()) {
return QString();
}
return field("assetAccountEdit").toStringList().first();
}
QDate KNewLoanWizard::initialPaymentDate() const
{
if (field("dontCreatePayoutCheckBox").toBool()) {
return QDate();
}
return field("paymentDate").toDate();
}
diff --git a/kmymoney/wizards/newloanwizard/loanattributeswizardpage.cpp b/kmymoney/wizards/newloanwizard/loanattributeswizardpage.cpp
index 700403028..61a9917f2 100644
--- a/kmymoney/wizards/newloanwizard/loanattributeswizardpage.cpp
+++ b/kmymoney/wizards/newloanwizard/loanattributeswizardpage.cpp
@@ -1,105 +1,106 @@
/***************************************************************************
loanattributeswizardpage - description
-------------------
begin : Mon Dec 30 2013
copyright : (C) 2013 by Jeremy Whiting
email : jpwhiting@kde.org
***************************************************************************/
/***************************************************************************
* *
* 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 "loanattributeswizardpage.h"
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
#include <KMessageBox>
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "knewbankdlg.h"
#include "mymoneyfile.h"
+#include "mymoneyexception.h"
LoanAttributesWizardPage::LoanAttributesWizardPage(QWidget *parent)
: LoanAttributesWizardPageDecl(parent)
{
// Register the fields with the QWizard and connect the
// appropriate signals to update the "Next" button correctly
registerField("institution", m_qcomboboxInstitutions);
connect(m_qcomboboxInstitutions, SIGNAL(currentIndexChanged(int)), this, SIGNAL(completeChanged()));
connect(m_qbuttonNew, SIGNAL(clicked()), this, SLOT(slotNewClicked()));
m_qcomboboxInstitutions->clear();
// Are we forcing the user to use institutions?
m_qcomboboxInstitutions->addItem(i18n("(No Institution)"));
try {
MyMoneyFile *file = MyMoneyFile::instance();
QList<MyMoneyInstitution> list;
file->institutionList(list);
qSort(list);
Q_FOREACH(const MyMoneyInstitution &institution, list) {
m_qcomboboxInstitutions->addItem(institution.name());
}
} catch (const MyMoneyException &e) {
qDebug("Exception in institution load: %s", qPrintable(e.what()));
}
}
/**
* Update the "Next" button
*/
bool LoanAttributesWizardPage::isComplete() const
{
return true;
}
void LoanAttributesWizardPage::initializePage()
{
}
void LoanAttributesWizardPage::setInstitution(const QString &institutionName)
{
if (institutionName.isEmpty()) {
m_qcomboboxInstitutions->setCurrentItem(i18n("(No Institution)"));
} else {
m_qcomboboxInstitutions->setCurrentItem(institutionName, false);
}
}
void LoanAttributesWizardPage::slotNewClicked()
{
MyMoneyInstitution institution;
QPointer<KNewBankDlg> dlg = new KNewBankDlg(institution, this);
if (dlg->exec() && dlg != 0) {
MyMoneyFileTransaction ft;
try {
MyMoneyFile *file = MyMoneyFile::instance();
institution = dlg->institution();
file->addInstitution(institution);
ft.commit();
initializePage();
m_qcomboboxInstitutions->setCurrentItem(institution.name(), false);
} catch (const MyMoneyException &) {
KMessageBox::information(this, i18n("Cannot add institution"));
}
}
delete dlg;
}
diff --git a/kmymoney/wizards/newloanwizard/paymentfrequencywizardpage.cpp b/kmymoney/wizards/newloanwizard/paymentfrequencywizardpage.cpp
index 8c5418e49..2fafb9741 100644
--- a/kmymoney/wizards/newloanwizard/paymentfrequencywizardpage.cpp
+++ b/kmymoney/wizards/newloanwizard/paymentfrequencywizardpage.cpp
@@ -1,37 +1,37 @@
/***************************************************************************
paymentfrequencywizardpage - description
-------------------
begin : Sun Jul 4 2010
copyright : (C) 2010 by Fernando Vilas
email : kmymoney-devel@kde.org
***************************************************************************/
/***************************************************************************
* *
* 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 "paymentfrequencywizardpage.h"
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyschedule.h"
PaymentFrequencyWizardPage::PaymentFrequencyWizardPage(QWidget *parent)
: PaymentFrequencyWizardPageDecl(parent)
{
registerField("paymentFrequencyUnitEdit", m_paymentFrequencyUnitEdit, "data", SIGNAL(currentDataChanged(QVariant)));
- m_paymentFrequencyUnitEdit->setCurrentIndex(m_paymentFrequencyUnitEdit->findData(QVariant(MyMoneySchedule::OCCUR_MONTHLY), Qt::UserRole, Qt::MatchExactly));
+ m_paymentFrequencyUnitEdit->setCurrentIndex(m_paymentFrequencyUnitEdit->findData(QVariant((int)eMyMoney::Schedule::Occurrence::Monthly), Qt::UserRole, Qt::MatchExactly));
}
diff --git a/kmymoney/wizards/newloanwizard/summarywizardpage.cpp b/kmymoney/wizards/newloanwizard/summarywizardpage.cpp
index 3242d36f6..2d77c611c 100644
--- a/kmymoney/wizards/newloanwizard/summarywizardpage.cpp
+++ b/kmymoney/wizards/newloanwizard/summarywizardpage.cpp
@@ -1,101 +1,103 @@
/***************************************************************************
summarywizardpage - description
-------------------
begin : Sun Jul 4 2010
copyright : (C) 2010 by Fernando Vilas
email : kmymoney-devel@kde.org
***************************************************************************/
/***************************************************************************
* *
* 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 "summarywizardpage.h"
// ----------------------------------------------------------------------------
// QT Includes
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyaccount.h"
#include "mymoneyexception.h"
#include "mymoneyfile.h"
+#include "mymoneypayee.h"
+#include "mymoneyschedule.h"
SummaryWizardPage::SummaryWizardPage(QWidget *parent)
: SummaryWizardPageDecl(parent)
{
// Register the fields with the QWizard and connect the
// appropriate signals to update the "Next" button correctly
}
void SummaryWizardPage::initializePage()
{
// General
if (field("borrowButton").toBool())
m_summaryLoanType->setText(i18n("borrowed"));
else
m_summaryLoanType->setText(i18n("lend"));
m_summaryFirstPayment->setText(QLocale().toString(field("firstDueDateEdit").toDate()));
const QString &payeeId = field("payeeEdit").toString();
if (!payeeId.isEmpty()) {
try {
const MyMoneyPayee &payee = MyMoneyFile::instance()->payee(payeeId);
m_summaryPayee->setText(payee.name());
} catch (const MyMoneyException &) {
qWarning("Unable to load the payee name from the id");
}
} else {
m_summaryPayee->setText(i18n("not assigned"));
}
// Calculation
if (field("interestOnReceptionButton").toBool())
m_summaryInterestDue->setText(i18n("on reception"));
else
m_summaryInterestDue->setText(i18n("on due date"));
- m_summaryPaymentFrequency->setText(MyMoneySchedule::occurrenceToString(MyMoneySchedule::occurrenceE(field("paymentFrequencyUnitEdit").toInt())));
+ m_summaryPaymentFrequency->setText(MyMoneySchedule::occurrenceToString(eMyMoney::Schedule::Occurrence(field("paymentFrequencyUnitEdit").toInt())));
m_summaryAmount->setText(field("loanAmount6").toString());
m_summaryInterestRate->setText(field("interestRate6").toString());
m_summaryTerm->setText(field("duration6").toString());
m_summaryPeriodicPayment->setText(field("payment6").toString());
m_summaryBalloonPayment->setText(field("balloon6").toString());
// Payment
try {
QStringList sel = field("interestAccountEdit").toStringList();
if (sel.count() != 1)
throw MYMONEYEXCEPTION("Need a single selected interest category");
MyMoneyAccount acc = MyMoneyFile::instance()->account(sel.first());
m_summaryInterestCategory->setText(acc.name());
} catch (const MyMoneyException &) {
qWarning("Unable to determine interest category for loan account creation");
}
m_summaryAdditionalFees->setText(field("additionalCost").toString());
m_summaryTotalPeriodicPayment->setText(field("periodicPayment").toString());
m_summaryNextPayment->setText(QLocale().toString(field("nextDueDateEdit").toDate()));
try {
QStringList sel = field("paymentAccountEdit").toStringList();
if (sel.count() != 1)
throw MYMONEYEXCEPTION("Need a single selected payment account");
MyMoneyAccount acc = MyMoneyFile::instance()->account(sel.first());
m_summaryPaymentAccount->setText(acc.name());
} catch (const MyMoneyException &) {
qWarning("Unable to determine payment account for loan account creation");
}
}
diff --git a/kmymoney/wizards/newuserwizard/knewuserwizard.cpp b/kmymoney/wizards/newuserwizard/knewuserwizard.cpp
index d77086239..b06716086 100644
--- a/kmymoney/wizards/newuserwizard/knewuserwizard.cpp
+++ b/kmymoney/wizards/newuserwizard/knewuserwizard.cpp
@@ -1,362 +1,362 @@
/***************************************************************************
knewuserwizard.cpp
-------------------
begin : Sat Feb 18 2006
copyright : (C) 2006 Thomas Baumgart
email : Thomas Baumgart <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 "knewuserwizard.h"
#include "knewuserwizard_p.h"
// ----------------------------------------------------------------------------
// QT Includes
#include <QIcon>
#include <QCheckBox>
#include <QPushButton>
#include <QDir>
#include <QLabel>
#include <QList>
#include <QFileInfo>
// ----------------------------------------------------------------------------
// KDE Includes
#include <KLocalizedString>
#include <KMessageBox>
#include <KUser>
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneysecurity.h"
#include "mymoneyfile.h"
#include <kguiutils.h>
#include "mymoneypayee.h"
#include "mymoneymoney.h"
#include "mymoneyinstitution.h"
#include "mymoneyaccount.h"
#include "kmymoneydateinput.h"
#include "kmymoneyedit.h"
#include <kaccounttemplateselector.h>
#include "kmymoneyglobalsettings.h"
#include "icons/icons.h"
using namespace Icons;
namespace NewUserWizard
{
static int stepCount;
Wizard::Wizard(QWidget *parent, bool modal, Qt::WindowFlags flags) :
KMyMoneyWizard(parent, modal, flags),
m_introPage(0)
{
bool isFirstTime = KMyMoneyGlobalSettings::firstTimeRun();
stepCount = 1;
setTitle(i18n("KMyMoney New File Setup"));
if (isFirstTime)
addStep(i18nc("New file wizard introduction", "Introduction"));
addStep(i18n("Personal Data"));
addStep(i18n("Select Currency"));
addStep(i18n("Select Accounts"));
addStep(i18n("Set preferences"));
addStep(i18nc("Finish the wizard", "Finish"));
if (isFirstTime)
m_introPage = new IntroPage(this);
m_generalPage = new GeneralPage(this);
m_currencyPage = new CurrencyPage(this);
m_accountPage = new AccountPage(this);
m_categoriesPage = new CategoriesPage(this);
m_preferencePage = new PreferencePage(this);
m_filePage = new FilePage(this);
m_accountPage->m_haveCheckingAccountButton->setChecked(true);
if (isFirstTime)
setFirstPage(m_introPage);
else
setFirstPage(m_generalPage);
setHelpContext("firsttime-3");
}
MyMoneyPayee Wizard::user() const
{
return m_generalPage->user();
}
QUrl Wizard::url() const
{
return m_filePage->m_dataFileEdit->url();
}
MyMoneyInstitution Wizard::institution() const
{
MyMoneyInstitution inst;
if (m_accountPage->m_haveCheckingAccountButton->isChecked()) {
if (m_accountPage->m_institutionNameEdit->text().length()) {
inst.setName(m_accountPage->m_institutionNameEdit->text());
if (m_accountPage->m_institutionNumberEdit->text().length())
inst.setSortcode(m_accountPage->m_institutionNumberEdit->text());
}
}
return inst;
}
MyMoneyAccount Wizard::account() const
{
MyMoneyAccount acc;
if (m_accountPage->m_haveCheckingAccountButton->isChecked()) {
acc.setName(m_accountPage->m_accountNameEdit->text());
if (m_accountPage->m_accountNumberEdit->text().length())
acc.setNumber(m_accountPage->m_accountNumberEdit->text());
acc.setOpeningDate(m_accountPage->m_openingDateEdit->date());
acc.setCurrencyId(m_baseCurrency.id());
- acc.setAccountType(MyMoneyAccount::Checkings);
+ acc.setAccountType(eMyMoney::Account::Checkings);
}
return acc;
}
MyMoneyMoney Wizard::openingBalance() const
{
return m_accountPage->m_openingBalanceEdit->value();
}
MyMoneySecurity Wizard::baseCurrency() const
{
return m_baseCurrency;
}
QList<MyMoneyTemplate> Wizard::templates() const
{
return m_categoriesPage->selectedTemplates();
}
bool Wizard::startSettingsAfterFinished() const
{
return m_preferencePage->m_openConfigAfterFinished->checkState() == Qt::Checked;
}
IntroPage::IntroPage(Wizard* wizard) :
KIntroPageDecl(wizard),
WizardPage<Wizard>(stepCount++, this, wizard)
{
}
KMyMoneyWizardPage* IntroPage::nextPage() const
{
return m_wizard->m_generalPage;
}
GeneralPage::GeneralPage(Wizard* wizard) :
UserInfo(wizard),
WizardPage<Wizard>(stepCount++, this, wizard),
m_contact(new MyMoneyContact(this))
{
m_userNameEdit->setFocus();
m_loadAddressButton->setEnabled(m_contact->ownerExists());
connect(m_loadAddressButton, SIGNAL(clicked()), this, SLOT(slotLoadFromAddressBook()));
}
void GeneralPage::slotLoadFromAddressBook()
{
m_userNameEdit->setText(m_contact->ownerFullName());
m_emailEdit->setText(m_contact->ownerEmail());
if (m_emailEdit->text().isEmpty()) {
KMessageBox::sorry(this, i18n("Unable to load data, because no contact has been associated with the owner of the standard address book."), i18n("Address book import"));
return;
}
m_loadAddressButton->setEnabled(false);
connect(m_contact, SIGNAL(contactFetched(ContactData)), this, SLOT(slotContactFetched(ContactData)));
m_contact->fetchContact(m_emailEdit->text());
}
void GeneralPage::slotContactFetched(const ContactData &identity)
{
m_loadAddressButton->setEnabled(true);
if (identity.email.isEmpty())
return;
m_telephoneEdit->setText(identity.phoneNumber);
QString sep;
if (!identity.country.isEmpty() && !identity.region.isEmpty())
sep = " / ";
m_countyEdit->setText(QString("%1%2%3").arg(identity.country, sep, identity.region));
m_postcodeEdit->setText(identity.postalCode);
m_townEdit->setText(identity.locality);
m_streetEdit->setText(identity.street);
}
KMyMoneyWizardPage* GeneralPage::nextPage() const
{
return m_wizard->m_currencyPage;
}
CurrencyPage::CurrencyPage(Wizard* wizard) :
Currency(wizard),
WizardPage<Wizard>(stepCount++, this, wizard)
{
QTreeWidgetItem *first = 0;
QList<MyMoneySecurity> list = MyMoneyFile::instance()->availableCurrencyList();
QList<MyMoneySecurity>::const_iterator it;
QString localCurrency(QLocale().currencySymbol(QLocale::CurrencyIsoCode));
QString baseCurrency = MyMoneyFile::instance()->baseCurrency().id();
m_currencyList->clear();
for (it = list.constBegin(); it != list.constEnd(); ++it) {
QTreeWidgetItem* p = insertCurrency(*it);
if ((*it).id() == baseCurrency) {
first = p;
QIcon icon = QIcon::fromTheme(g_Icons[Icon::ViewBankAccount]);
p->setIcon(0, icon);
} else {
p->setIcon(0, QIcon());
}
if (!first && (*it).id() == localCurrency)
first = p;
}
QTreeWidgetItemIterator itemsIt = QTreeWidgetItemIterator(m_currencyList, QTreeWidgetItemIterator::All);
if (first == 0)
first = *itemsIt;
if (first != 0) {
m_currencyList->setCurrentItem(first);
m_currencyList->setItemSelected(first, true);
m_currencyList->scrollToItem(first, QTreeView::PositionAtTop);
}
}
void CurrencyPage::enterPage()
{
m_currencyList->setFocus();
}
KMyMoneyWizardPage* CurrencyPage::nextPage() const
{
QString selCur = selectedCurrency();
QList<MyMoneySecurity> currencies = MyMoneyFile::instance()->availableCurrencyList();
foreach (auto currency, currencies) {
if (selCur == currency.id()) {
m_wizard->m_baseCurrency = currency;
break;
}
}
m_wizard->m_accountPage->m_accountCurrencyLabel->setText(m_wizard->m_baseCurrency.tradingSymbol());
return m_wizard->m_accountPage;
}
AccountPage::AccountPage(Wizard* wizard) :
KAccountPageDecl(wizard),
WizardPage<Wizard>(stepCount, this, wizard) // don't inc. the step count here
{
m_mandatoryGroup->add(m_accountNameEdit);
connect(m_mandatoryGroup, SIGNAL(stateChanged()), object(), SIGNAL(completeStateChanged()));
connect(m_haveCheckingAccountButton, SIGNAL(toggled(bool)), object(), SIGNAL(completeStateChanged()));
m_accountNameEdit->setFocus();
m_openingDateEdit->setDate(QDate(QDate::currentDate().year(), 1, 1));
}
KMyMoneyWizardPage* AccountPage::nextPage() const
{
return m_wizard->m_categoriesPage;
}
bool AccountPage::isComplete() const
{
return !m_haveCheckingAccountButton->isChecked() || m_mandatoryGroup->isEnabled();
}
CategoriesPage::CategoriesPage(Wizard* wizard) :
Accounts(wizard),
WizardPage<Wizard>(stepCount++, this, wizard)
{
}
KMyMoneyWizardPage* CategoriesPage::nextPage() const
{
return m_wizard->m_preferencePage;
}
QList<MyMoneyTemplate> CategoriesPage::selectedTemplates() const
{
return m_templateSelector->selectedTemplates();
}
PreferencePage::PreferencePage(Wizard* wizard) :
KPreferencePageDecl(wizard),
WizardPage<Wizard>(stepCount++, this, wizard)
{
}
KMyMoneyWizardPage* PreferencePage::nextPage() const
{
return m_wizard->m_filePage;
}
FilePage::FilePage(Wizard* wizard) :
KFilePageDecl(wizard),
WizardPage<Wizard>(stepCount++, this, wizard)
{
m_mandatoryGroup->add(m_dataFileEdit->lineEdit());
connect(m_mandatoryGroup, SIGNAL(stateChanged()), object(), SIGNAL(completeStateChanged()));
KUser user;
QString folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
if (folder.isEmpty() || !QDir().exists(folder))
folder = QDir::homePath();
m_dataFileEdit->setStartDir(QUrl::fromLocalFile(folder));
m_dataFileEdit->setUrl(QUrl::fromLocalFile(folder + QLatin1Char('/') + user.loginName() + QLatin1String(".kmy")));
m_dataFileEdit->setFilter(i18n("*.kmy *.xml|KMyMoney files\n*|All files"));
m_dataFileEdit->setMode(KFile::File);
}
bool FilePage::isComplete() const
{
//! @todo Allow to overwrite files
bool rc = m_mandatoryGroup->isEnabled();
m_existingFileLabel->hide();
m_finishLabel->show();
if (rc) {
// if a filename is present, check that
// a) the file does not exist
// b) the directory does exist
// c) the file is stored locally (because we cannot check previous conditions if it is not)
const QUrl fullPath = m_dataFileEdit->url();
QFileInfo directory{fullPath.adjusted(QUrl::RemoveFilename).toLocalFile()};
qDebug() << "Selected fileptah: " << fullPath << " " << directory.absoluteFilePath() << " dir: " << directory.isDir();
rc = false;
if (!fullPath.isValid() || !fullPath.isLocalFile()) {
m_dataFileEdit->setToolTip(i18n("The path has to be valid and cannot be on a remote location."));
} else if (QFileInfo::exists(fullPath.toLocalFile())) {
m_dataFileEdit->setToolTip(i18n("The file exists already. Please create a new file."));
} else if (!directory.isDir()) {
m_dataFileEdit->setToolTip(i18n("The destination directory does not exist or cannot be written to."));
} else {
m_dataFileEdit->setToolTip("");
rc = true;
}
m_existingFileLabel->setHidden(rc);
m_finishLabel->setVisible(rc);
}
return rc;
}
}
diff --git a/tools/xea2kmt.cpp b/tools/xea2kmt.cpp
index 976784b29..f40262115 100644
--- a/tools/xea2kmt.cpp
+++ b/tools/xea2kmt.cpp
@@ -1,650 +1,653 @@
/***************************************************************************
xea2kmt.cpp
-------------------
copyright : (C) 2014 by Ralf Habacker <ralf.habacker@freenet.de>
****************************************************************************/
/***************************************************************************
* *
* 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 "../kmymoney/mymoney/mymoneyaccount.h"
#include <QDir>
#include <QFile>
#include <QStringList>
#include <QMap>
#include <QTextStream>
#include <QXmlStreamReader>
#include <QDebug>
+#include "mymoneyenums.h"
+
+using namespace eMyMoney;
QDebug operator <<(QDebug out, const QXmlStreamNamespaceDeclaration &a)
{
out << "QXmlStreamNamespaceDeclaration("
<< "prefix:" << a.prefix().toString()
<< "namespaceuri:" << a.namespaceUri().toString()
<< ")";
return out;
}
QDebug operator <<(QDebug out, const QXmlStreamAttribute &a)
{
out << "QXmlStreamAttribute("
<< "prefix:" << a.prefix().toString()
<< "namespaceuri:" << a.namespaceUri().toString()
<< "name:" << a.name().toString()
<< " value:" << a.value().toString()
<< ")";
return out;
}
bool debug = false;
bool withID = false;
bool noLevel1Names = false;
bool withTax = false;
bool prefixNameWithCode = false;
typedef QMap<QString,QString> DirNameMapType;
/**
* map to hold differences from gnucash to kmymoney template directory
* @return directory name map
*/
DirNameMapType &getDirNameMap()
{
static DirNameMapType dirNameMap;
dirNameMap["cs"] = "cs_CZ";
dirNameMap["da"] = "dk";
dirNameMap["ja"] = "ja_JP";
dirNameMap["ko"] = "ko_KR";
dirNameMap["nb"] = "nb_NO";
dirNameMap["nl"] = "nl_NL";
dirNameMap["ru"] = "ru_RU";
return dirNameMap;
}
int toKMyMoneyAccountType(const QString &type)
{
- if(type == "ROOT") return MyMoneyAccount::UnknownAccountType;
- else if (type == "BANK") return MyMoneyAccount::Checkings;
- else if (type == "CASH") return MyMoneyAccount::Cash;
- else if (type == "CREDIT") return MyMoneyAccount::CreditCard;
- else if (type == "INVEST") return MyMoneyAccount::Investment;
- else if (type == "RECEIVABLE") return MyMoneyAccount::Asset;
- else if (type == "ASSET") return MyMoneyAccount::Asset;
- else if (type == "PAYABLE") return MyMoneyAccount::Liability;
- else if (type == "LIABILITY") return MyMoneyAccount::Liability;
- else if (type == "CURRENCY") return MyMoneyAccount::Currency;
- else if (type == "INCOME") return MyMoneyAccount::Income;
- else if (type == "EXPENSE") return MyMoneyAccount::Expense;
- else if (type == "STOCK") return MyMoneyAccount::Stock;
- else if (type == "MUTUAL") return MyMoneyAccount::Stock;
- else if (type == "EQUITY") return MyMoneyAccount::Equity;
+ if(type == "ROOT") return (int)Account::Unknown;
+ else if (type == "BANK") return (int)Account::Checkings;
+ else if (type == "CASH") return (int)Account::Cash;
+ else if (type == "CREDIT") return (int)Account::CreditCard;
+ else if (type == "INVEST") return (int)Account::Investment;
+ else if (type == "RECEIVABLE") return (int)Account::Asset;
+ else if (type == "ASSET") return (int)Account::Asset;
+ else if (type == "PAYABLE") return (int)Account::Liability;
+ else if (type == "LIABILITY") return (int)Account::Liability;
+ else if (type == "CURRENCY") return (int)Account::Currency;
+ else if (type == "INCOME") return (int)Account::Income;
+ else if (type == "EXPENSE") return (int)Account::Expense;
+ else if (type == "STOCK") return (int)Account::Stock;
+ else if (type == "MUTUAL") return (int)Account::Stock;
+ else if (type == "EQUITY") return (int)Account::Equity;
else return 99; // unknown
}
class TemplateAccount {
public:
typedef QList<TemplateAccount> List;
typedef QList<TemplateAccount*> PointerList;
typedef QMap<QString,QString> SlotList;
QString id;
QString type;
QString name;
QString code;
QString parent;
SlotList slotList;
TemplateAccount()
{
}
TemplateAccount(const TemplateAccount &b)
: id(b.id),
type(b.type),
name(b.name),
code(b.code),
parent(b.parent),
slotList(b.slotList)
{
}
void clear()
{
id = "";
type = "";
name = "";
code = "";
parent = "";
slotList.clear();
}
bool readSlots(QXmlStreamReader &xml)
{
while (!xml.atEnd()) {
QXmlStreamReader::TokenType type = xml.readNext();
if (type == QXmlStreamReader::StartElement) {
QStringRef _name = xml.name();
if (_name == "slot") {
type = xml.readNext();
if (type == QXmlStreamReader::Characters)
type = xml.readNext();
if (type == QXmlStreamReader::StartElement) {
QStringRef name = xml.name();
QString key, value;
if (name == "key")
key = xml.readElementText(QXmlStreamReader::SkipChildElements).trimmed();
type = xml.readNext();
if (type == QXmlStreamReader::Characters)
type = xml.readNext();
if (type == QXmlStreamReader::StartElement) {
name = xml.name();
if (name == "value")
value = xml.readElementText(QXmlStreamReader::SkipChildElements).trimmed();
}
if (!key.isEmpty() && !value.isEmpty())
slotList[key] = value;
}
}
} else if (type == QXmlStreamReader::EndElement) {
QStringRef _name = xml.name();
if (_name == "slots")
return true;
}
}
return true;
}
bool read(QXmlStreamReader &xml)
{
while (!xml.atEnd()) {
xml.readNext();
QStringRef _name = xml.name();
if (xml.isEndElement() && _name == "account") {
if (prefixNameWithCode && !code.isEmpty() && !name.startsWith(code))
name = code + " " + name;
return true;
}
if (xml.isStartElement())
{
if (_name == "name")
name = xml.readElementText(QXmlStreamReader::SkipChildElements).trimmed();
else if (_name == "id")
id = xml.readElementText(QXmlStreamReader::SkipChildElements).trimmed();
else if (_name == "type")
type = xml.readElementText(QXmlStreamReader::SkipChildElements).trimmed();
else if (_name == "code")
code = xml.readElementText(QXmlStreamReader::SkipChildElements).trimmed();
else if (_name == "parent")
parent = xml.readElementText(QXmlStreamReader::SkipChildElements).trimmed();
else if (_name == "slots")
readSlots(xml);
else
{
if (debug)
qDebug() << "skipping" << _name.toString();
}
}
}
return false;
}
};
QDebug operator <<(QDebug out, const TemplateAccount &a)
{
out << "TemplateAccount("
<< "name:" << a.name
<< "id:" << a.id
<< "type:" << a.type
<< "code:" << a.code
<< "parent:" << a.parent
<< "slotList:" << a.slotList
<< ")\n";
return out;
}
QDebug operator <<(QDebug out, const TemplateAccount::PointerList &a)
{
out << "TemplateAccount::List(";
foreach(const TemplateAccount *account, a)
out << *account;
out << ")";
return out;
}
class TemplateFile {
public:
QString title;
QString longDescription;
QString shortDescription;
TemplateAccount::List accounts;
bool read(QXmlStreamReader &xml)
{
Q_ASSERT(xml.isStartElement() && xml.name() == "gnc-account-example");
while (xml.readNextStartElement()) {
QStringRef name = xml.name();
if (xml.name() == "title")
title = xml.readElementText().trimmed();
else if (xml.name() == "short-description")
shortDescription = xml.readElementText().trimmed().replace(" ", " ");
else if (xml.name() == "long-description")
longDescription = xml.readElementText().trimmed().replace(" ", " ");
else if (xml.name() == "account")
{
TemplateAccount account;
if (account.read(xml))
accounts.append(account);
}
else
{
if (debug)
qDebug() << "skipping" << name.toString();
xml.skipCurrentElement();
}
}
return true;
}
bool writeAsXml(QXmlStreamWriter &xml)
{
xml.writeStartElement("","title");
xml.writeCharacters(title);
xml.writeEndElement();
xml.writeStartElement("","shortdesc");
xml.writeCharacters(shortDescription);
xml.writeEndElement();
xml.writeStartElement("","longdesc");
xml.writeCharacters(longDescription);
xml.writeEndElement();
xml.writeStartElement("","accounts");
bool result = writeAccountsAsXml(xml);
xml.writeEndElement();
return result;
}
bool writeAccountsAsXml(QXmlStreamWriter &xml, const QString &id="", int index=0)
{
TemplateAccount::PointerList list;
if (index == 0)
list = accountsByType("ROOT");
else
list = accountsByParentID(id);
foreach(TemplateAccount *account, list)
{
if (account->type != "ROOT")
{
xml.writeStartElement("","account");
xml.writeAttribute("type", QString::number(toKMyMoneyAccountType(account->type)));
xml.writeAttribute("name", noLevel1Names && index < 2 ? "" : account->name);
if (withID)
xml.writeAttribute("id", account->id);
if (withTax) {
if (account->slotList.contains("tax-related")) {
xml.writeStartElement("flag");
xml.writeAttribute("name","Tax");
xml.writeAttribute("value",account->slotList["tax-related"]);
xml.writeEndElement();
}
}
}
index++;
writeAccountsAsXml(xml, account->id, index);
index--;
xml.writeEndElement();
}
return true;
}
TemplateAccount *account(const QString &id)
{
for(int i=0; i < accounts.size(); i++)
{
TemplateAccount &account = accounts[i];
if (account.id == id)
return &account;
}
return 0;
}
TemplateAccount::PointerList accountsByType(const QString &type)
{
TemplateAccount::PointerList list;
for(int i=0; i < accounts.size(); i++)
{
TemplateAccount &account = accounts[i];
if (account.type == type)
list.append(&account);
}
return list;
}
static bool nameLessThan(TemplateAccount *a1, TemplateAccount *a2)
{
return a1->name < a2->name;
}
TemplateAccount::PointerList accountsByParentID(const QString &parentID)
{
TemplateAccount::PointerList list;
for(int i=0; i < accounts.size(); i++)
{
TemplateAccount &account = accounts[i];
if (account.parent == parentID)
list.append(&account);
}
qSort(list.begin(), list.end(), nameLessThan);
return list;
}
bool dumpTemplates(const QString &id="", int index=0)
{
TemplateAccount::PointerList list;
if (index == 0)
list = accountsByType("ROOT");
else
list = accountsByParentID(id);
foreach(TemplateAccount *account, list)
{
QString a;
a.fill(' ', index);
qDebug() << a << account->name << toKMyMoneyAccountType(account->type);
index++;
dumpTemplates(account->id, index);
index--;
}
return true;
}
};
QDebug operator <<(QDebug out, const TemplateFile &a)
{
out << "TemplateFile("
<< "title:" << a.title
<< "short description:" << a.shortDescription
<< "long description:" << a.longDescription
<< "accounts:";
foreach(const TemplateAccount &account, a.accounts)
out << account;
out << ")";
return out;
}
class GnuCashAccountTemplateReader {
public:
GnuCashAccountTemplateReader()
{
}
bool read(const QString &filename)
{
QFile file(filename);
QTextStream in(&file);
in.setCodec("utf-8");
if(!file.open(QIODevice::ReadOnly))
return false;
inFileName = filename;
return read(in.device());
}
TemplateFile &result()
{
return _template;
}
bool dumpTemplates()
{
return _template.dumpTemplates();
}
bool writeAsXml(const QString &filename=QString())
{
if (filename.isEmpty())
{
QTextStream stream(stdout);
return writeAsXml(stream.device());
}
else
{
QFile file(filename);
if(!file.open(QIODevice::WriteOnly))
return false;
return writeAsXml(&file);
}
}
protected:
bool checkAndUpdateAvailableNamespaces(QXmlStreamReader &xml)
{
if (xml.namespaceDeclarations().size() < 5)
{
qWarning() << "gnucash template file is missing required name space declarations; adding by self";
}
xml.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("act", "http://www.gnucash.org/XML/act"));
xml.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("gnc", "http://www.gnucash.org/XML/gnc"));
xml.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("gnc-act", "http://www.gnucash.org/XML/gnc-act"));
xml.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("cmdty","http://www.gnucash.org/XML/cmdty"));
xml.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("slot","http://www.gnucash.org/XML/slot"));
return true;
}
bool read(QIODevice *device)
{
xml.setDevice(device);
while(!xml.atEnd())
{
xml.readNext();
if (xml.isStartElement())
{
if (xml.name() == "gnc-account-example")
{
checkAndUpdateAvailableNamespaces(xml);
_template.read(xml);
}
else
xml.raiseError(QObject::tr("The file is not an gnucash account template file."));
}
}
if (xml.error() != QXmlStreamReader::NoError)
qWarning() << xml.errorString();
return !xml.error();
}
bool writeAsXml(QIODevice *device)
{
QXmlStreamWriter xml(device);
xml.setAutoFormatting(true);
xml.setAutoFormattingIndent(1);
xml.setCodec("utf-8");
xml.writeStartDocument();
QString fileName = inFileName;
fileName.replace(QRegExp(".*/accounts"),"accounts");
xml.writeComment(QString("\n"
" Converted using xea2kmt from GnuCash sources\n"
"\n"
" %1\n"
"\n"
" Please check the source file for possible copyright\n"
" and license information.\n"
).arg(fileName));
xml.writeDTD("<!DOCTYPE KMYMONEY-TEMPLATE>");
xml.writeStartElement("","kmymoney-account-template");
bool result = _template.writeAsXml(xml);
xml.writeEndElement();
xml.writeEndDocument();
return result;
}
QXmlStreamReader xml;
TemplateFile _template;
QString inFileName;
};
void scanDir(QDir dir, QStringList &files)
{
dir.setNameFilters(QStringList("*.gnucash-xea"));
dir.setFilter(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks);
if (debug)
qDebug() << "Scanning: " << dir.path();
QStringList fileList = dir.entryList();
for (int i=0; i<fileList.count(); i++)
{
if (debug)
qDebug() << "Found file: " << fileList[i];
files.append(QString("%1/%2").arg(dir.absolutePath()).arg(fileList[i]));
}
dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
QStringList dirList = dir.entryList();
for (int i=0; i<dirList.size(); ++i)
{
QString newPath = QString("%1/%2").arg(dir.absolutePath()).arg(dirList.at(i));
scanDir(QDir(newPath), files);
}
}
bool convertFile(const QString &inFile, const QString &outFile)
{
GnuCashAccountTemplateReader reader;
if (!reader.read(inFile))
return false;
return reader.writeAsXml(outFile);
}
int convertFileStructure(const QString &indir, const QString &outdir)
{
DirNameMapType &dirNameMap = getDirNameMap();
// get gnucash account files
QDir d(indir);
QStringList files;
scanDir(d, files);
QString inPath = d.absolutePath();
QDir outDir(outdir);
QString outPath = outDir.absolutePath();
QStringList mapKeys = dirNameMap.keys();
int result = 0;
// process templates
foreach (const QString &file, files)
{
if (debug)
qDebug() << "processing" << file;
// create output file dir
QFileInfo fi(file);
QString outFileName = fi.canonicalFilePath().replace(inPath, outPath).replace("acctchrt_", "").replace(".gnucash-xea", ".kmt");
foreach(const QString &key, mapKeys)
{
if (outFileName.contains("/" + key + "/"))
outFileName = outFileName.replace("/" + key + "/", "/" + dirNameMap[key] + "/");
}
fi.setFile(outFileName);
QDir d(fi.absolutePath());
if (!d.exists())
{
if (debug)
qDebug() << "creating path " << fi.absolutePath();
d.mkpath(fi.absolutePath());
}
if (debug)
qDebug() << "writing to " << outFileName;
if (!convertFile(file, outFileName))
{
qWarning() << "could not create" << outFileName;
result = 1;
}
}
return result;
}
int main(int argc, char *argv[])
{
if (argc < 2 || (argc == 2 && QLatin1String(argv[1]) == "--help"))
{
qWarning() << "xea2kmt: convert gnucash template file to kmymoney template file";
qWarning() << argv[0] << "<options> <gnucash-template-file> [<kmymoney-template-output-file>]";
qWarning() << argv[0] << "<options> --in-dir <gnucash-template-files-root> --out-dir <kmymoney-template-files-root>";
qWarning() << "options:";
qWarning() << " --debug - output debug information";
qWarning() << " --help - this page";
qWarning() << " --no-level1-names - do not export account names for top level accounts";
qWarning() << " --prefix-name-with-code - prefix account name with account code if present";
qWarning() << " --with-id - write account id attribute";
qWarning() << " --with-tax-related - parse and export gnucash 'tax-related' flag";
qWarning() << " --in-dir <dir> - search for gnucash templates files in <dir>";
qWarning() << " --out-dir <dir> - generate kmymoney templates below <dir";
return -1;
}
QString inFileName;
QString outFileName;
QString inDir;
QString outDir;
for(int i = 1; i < argc; i++)
{
QString arg = QLatin1String(argv[i]);
if (arg == "--debug")
debug = true;
else if (arg == "--with-id")
withID = true;
else if (arg == "--no-level1-names")
noLevel1Names = true;
else if (arg == "--with-tax-related")
withTax = true;
else if (arg == "--prefix-name-with-code")
prefixNameWithCode = true;
else if (arg == "--in-dir")
inDir = argv[++i];
else if (arg == "--out-dir")
outDir = argv[++i];
else if (!arg.startsWith(QLatin1String("--")))
{
if (inFileName.isEmpty())
inFileName = arg;
else
outFileName = arg;
}
else
{
qWarning() << "invalid command line parameter'" << arg << "'";
return -1;
}
}
if (!inDir.isEmpty() && !outDir.isEmpty())
{
return convertFileStructure(inDir, outDir);
}
GnuCashAccountTemplateReader reader;
bool result = reader.read(inFileName);
if (debug)
{
qDebug() << reader.result();
reader.dumpTemplates();
}
reader.writeAsXml(outFileName);
return result ? 0 : -2;
}