summaryrefslogtreecommitdiffstats
path: root/kmymoney2/converter/mymoneygncreader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kmymoney2/converter/mymoneygncreader.cpp')
-rw-r--r--kmymoney2/converter/mymoneygncreader.cpp2463
1 files changed, 2463 insertions, 0 deletions
diff --git a/kmymoney2/converter/mymoneygncreader.cpp b/kmymoney2/converter/mymoneygncreader.cpp
new file mode 100644
index 0000000..40933e3
--- /dev/null
+++ b/kmymoney2/converter/mymoneygncreader.cpp
@@ -0,0 +1,2463 @@
+/***************************************************************************
+ 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+#include <qfile.h>
+#include <qmap.h>
+#include <qobject.h>
+#include <qfiledialog.h>
+#include <qinputdialog.h>
+#include <qdatetime.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+#ifndef _GNCFILEANON
+ #include <klocale.h>
+ #include <kconfig.h>
+ #include <kmessagebox.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// Third party Includes
+
+// ------------------------------------------------------------Box21----------------
+// Project Includes
+#include "mymoneygncreader.h"
+#ifndef _GNCFILEANON
+ #include "config.h"
+ #include "../mymoney/storage/imymoneystorage.h"
+ #include "../kmymoneyutils.h"
+ #include "../mymoney/mymoneyfile.h"
+ #include "../mymoney/mymoneyprice.h"
+ #include "../dialogs/kgncimportoptionsdlg.h"
+ #include "../dialogs/kgncpricesourcedlg.h"
+ #include "../dialogs/keditscheduledlg.h"
+ #include "../widgets/kmymoneyedit.h"
+ #define TRY try {
+ #define CATCH } catch (MyMoneyException *e) {
+ #define PASS } catch (MyMoneyException *e) { throw e; }
+#else
+ #include "mymoneymoney.h"
+ #include <qtextedit.h>
+ #define i18n QObject::tr
+ #define TRY
+ #define CATCH
+ #define PASS
+ #define MYMONEYEXCEPTION QString
+ #define MyMoneyException QString
+ #define PACKAGE "KMyMoney"
+#endif // _GNCFILEANON
+
+// 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 () {
+ m_v.setAutoDelete (true);
+}
+
+// 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 = i18n("%1: Sorry. This importer cannot handle version %2 of element %3")
+ .arg(__func__).arg(elAttrs.value("version")).arg(elName);
+ throw new 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 {
+ return (pMain->m_decoder == 0
+ ? *(m_v.at(i))
+ : pMain->m_decoder->toUnicode (*(m_v.at(i))));
+}
+
+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>::Iterator it;
+ MyMoneyMoney in, mresult;
+ switch (anonClass) {
+ case ASIS: break; // this is not personal data
+ case SUPPRESS: result = ""; break; // this is personal and is not essential
+ case NXTACC: result = i18n("Account%1").arg(++nextAccount, -6); break; // generate account name
+ case NXTEQU: // generate/return an equity name
+ it = anonStocks.find (data);
+ if (it == anonStocks.end()) {
+ result = i18n("Stock%1").arg(++nextEquity, -6);
+ anonStocks.insert (data, result);
+ } else {
+ result = (*it).data();
+ }
+ break;
+ case NXTPAY: // genearet/return a payee name
+ it = anonPayees.find (data);
+ if (it == anonPayees.end()) {
+ result = i18n("Payee%1").arg(++nextPayee, -6);
+ anonPayees.insert (data, result);
+ } else {
+ result = (*it).data();
+ }
+ break;
+ case NXTSCHD: result = i18n("Schedule%1").arg(++nextSched, -6); break; // generate a schedule name
+ case MONEY1:
+ in = MyMoneyMoney(data);
+ if (data == "-1/0") in = MyMoneyMoney (0); // 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 (0);
+ 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 %s", m_elementName.latin1());
+ for (i = 0; i < m_dataElementListCount; i++) {
+ qDebug ("%s = %s", m_dataElementList[i].latin1(), m_v.at(i)->latin1());
+ }
+}
+//*****************************************************************
+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 new 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 new 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 (new 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 (new 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 (new QString (""));
+ m_kvpList.setAutoDelete (true);
+}
+
+GncKvp::~GncKvp () {}
+
+void GncKvp::dataEl (const QXmlAttributes& elAttrs) {
+ switch (m_state) {
+ case VALUE:
+ m_kvpType = elAttrs.value("type");
+ }
+ m_dataPtr = m_v.at(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 new MYMONEYEXCEPTION ("GncKvp rcvd invalid m_state ");
+ }
+ return (next);
+ PASS
+}
+
+void GncKvp::endSubEl(GncObject *subObj) {
+ if (pMain->xmldebug) qDebug ("Kvp end subel");
+ m_kvpList.append (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 (new 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.at(0);
+ return ;
+}
+
+void GncCountData::terminate () {
+ int i = m_v.at(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 %s", m_countType.latin1());
+ }
+ 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 (new 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 (new QString (""));
+ m_vpCommodity = NULL;
+ m_vpCurrency = NULL;
+ m_vpPriceDate = NULL;
+}
+
+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 new 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 new 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;
+ m_kvpList.setAutoDelete (true);
+ for (uint i = 0; i < m_dataElementListCount; i++) m_v.append (new QString (""));
+ m_vpCommodity = NULL;
+}
+
+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 new 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 (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;
+ m_splitList.setAutoDelete (true);
+ for (uint i = 0; i < m_dataElementListCount; i++) m_v.append (new QString (""));
+ m_vpCurrency = NULL;
+ m_vpDateEntered = m_vpDatePosted = NULL;
+}
+
+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 new 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 (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 (new QString (""));
+ m_vpDateReconciled = NULL;
+}
+
+GncSplit::~GncSplit () {
+ delete m_vpDateReconciled;
+}
+
+GncObject *GncSplit::startSubEl () {
+ TRY
+ GncObject *next = 0;
+ switch (m_state) {
+ case RECDATE: next = new GncDate; break;
+ default: throw new 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 (new QString (""));
+ m_kvpList.setAutoDelete (true);
+}
+
+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 new MYMONEYEXCEPTION ("GncTemplateSplit rcvd invalid m_state");
+ }
+ return (next);
+ PASS
+}
+
+void GncTemplateSplit::endSubEl(GncObject *subObj) {
+ if (pMain->xmldebug) qDebug ("TemplateSplit end subel");
+ m_kvpList.append (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 (new QString (""));
+ m_vpStartDate = m_vpLastDate = m_vpEndDate = NULL;
+ m_vpFreqSpec = NULL;
+ m_vpRecurrence.clear();
+ m_vpRecurrence.setAutoDelete(true);
+ m_vpSchedDef = NULL;
+}
+
+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 new 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 (new QString (""));
+ m_fsList.setAutoDelete (true);
+}
+
+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 new 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_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 (new 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 new 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") and (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
+************************************************************************************************/
+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 new MYMONEYEXCEPTION (i18n("Input file cannot be parsed; may be corrupt\n%s", errorString().latin1()));
+ }
+ delete m_reader;
+ delete m_source;
+ return ;
+}
+
+// XML handling routines
+bool XmlReader::startDocument() {
+ m_os.setAutoDelete (true);
+ 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 - %s", elName.latin1());
+#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.latin1();
+ 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 new 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 (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").arg(e->what()), PACKAGE);
+ qFatal ("%s", e->what().latin1());
+#else
+ qFatal ("%s", e->latin1());
+#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 - %s", elName.latin1());
+#ifdef _GNCFILEANON
+ QString spaces;
+ switch (lastType) {
+ case 2:
+ indentCount -= 2; spaces.fill (' ', indentCount); pMain->oStream << endl << spaces.latin1(); 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 (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").arg(e->what()), PACKAGE);
+ qFatal ("%s", e->what().latin1());
+#else
+ qFatal ("%s", e->latin1());
+#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.stripWhiteSpace(); // data may contain line feeds and indentation spaces
+ if (!pData.isEmpty()) {
+ if (pMain->developerDebug) qDebug ("XML Data - %s", pData.latin1());
+ 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() {
+#ifndef _GNCFILEANON
+ m_storage = NULL;
+ m_messageList.setAutoDelete (true);
+ m_templateList.setAutoDelete (true);
+#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"));
+
+ MyMoneyFileTransaction ft;
+ m_xr = new XmlReader (this);
+ try {
+ m_xr->processFile (pDevice);
+ terminate (); // do all the wind-up things
+ ft.commit();
+ } catch (MyMoneyException *e) {
+ KMessageBox::error(0, i18n("Import failed:\n\n%1").arg(e->what()), PACKAGE);
+ qFatal ("%s", e->what().latin1());
+ } // end catch
+ signalProgress (0, 1, i18n("Import complete")); // switch off progress bar
+ delete m_xr;
+ qDebug ("Exiting gnucash importer");
+ return ;
+}
+#else
+// Control code for the file anonymizer
+void MyMoneyGncReader::readFile(QString in, QString out) {
+ QFile pDevice (in);
+ if (!pDevice.open (IO_ReadOnly)) qFatal ("Can't open input file");
+ QFile outFile (out);
+ if (!outFile.open (IO_WriteOnly)) qFatal ("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 (MyMoneyException *e) {
+ qFatal ("%s", e->latin1());
+ } // end catch
+ delete m_xr;
+ pDevice.close();
+ outFile.close();
+ return ;
+}
+
+#include <qapplication.h>
+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 = QFileDialog::getOpenFileName("",
+ "Gnucash files(*.nc *)",
+ 0);
+ }
+ if (inFile.isEmpty()) qFatal ("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 (
+ 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.").arg(MINFILEHIDEF).arg(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 (MyMoneySecurity::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 = %s, id = %s", gcm->id().latin1(), equ.id().data());
+ m_mapEquities[gcm->id().utf8()] = 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().utf8(), gpr->currency()->id().utf8(),
+ gpr->priceDate(), rate, i18n("Imported History"));
+ m_storage->addPrice (exchangeRate);
+ } else {
+ MyMoneySecurity e = m_storage->security(m_mapEquities[gpr->commodity()->id().utf8()]);
+ if (gncdebug) qDebug ("Searching map, key = %s, found id = %s",
+ gpr->commodity()->id().latin1(), e.id().data());
+ e.setTradingCurrency (gpr->currency()->id().utf8());
+ MyMoneyPrice stockPrice(e.id(), gpr->currency()->id().utf8(), gpr->priceDate(), rate, i18n("Imported History"));
+ 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().utf8();
+ 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().utf8());
+ m_currencyCount[gac->commodity()->id()]++;
+ }
+
+ acc.setParentAccountId (gac->parent().utf8());
+ // 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);
+ } else if ("SAVINGS" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::Savings);
+ } else if ("ASSET" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::Asset);
+ } else if ("CASH" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::Cash);
+ } else if ("CURRENCY" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::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);
+ } else {
+ acc.setAccountType(MyMoneyAccount::Stock);
+ }
+ } else if ("EQUITY" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::Equity);
+ } else if ("LIABILITY" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::Liability);
+ } else if ("CREDIT" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::CreditCard);
+ } else if ("INCOME" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::Income);
+ } else if ("EXPENSE" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::Expense);
+ } else if ("RECEIVABLE" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::Asset);
+ } else if ("PAYABLE" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::Liability);
+ } else if ("MONEYMRKT" == gac->type()) {
+ acc.setAccountType(MyMoneyAccount::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").arg(gac->type());
+ throw new 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: acc.setParentAccountId (m_storage->asset().id()); break;
+ case MyMoneyAccount::Liability: acc.setParentAccountId (m_storage->liability().id()); break;
+ case MyMoneyAccount::Income: acc.setParentAccountId (m_storage->income().id()); break;
+ case MyMoneyAccount::Expense: acc.setParentAccountId (m_storage->expense().id()); break;
+ case MyMoneyAccount::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) {
+ // 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().utf8()]);
+ if (gncdebug) qDebug ("Acct equity search, key = %s, found id = %s",
+ gac->commodity()->id().latin1(), e.id().data());
+ acc.setCurrencyId (e.id()); // actually, the security id
+ if ("MUTUAL" == gac->type()) {
+ e.setSecurityType (MyMoneySecurity::SECURITY_MUTUALFUND);
+ if (gncdebug) qDebug ("Setting %s to mutual", e.name().latin1());
+ m_storage->modifySecurity (e);
+ }
+ // See if he wants online quotes for this account
+ // NB: In gnc, this selection is per account, in KMM, per security
+ // This is unlikely to cause problems in practice. If it does,
+ // we probably need to introduce a 'pricing basis' in the account class
+ QPtrListIterator<GncObject> kvpi (gac->m_kvpList);
+ GncKvp *k;
+ while ((k = static_cast<GncKvp *>(kvpi.current())) != 0) {
+ if (k->key().contains("price-source") && k->type() == "string") {
+ getPriceSource (e, k->value());
+ break;
+ } else {
+ ++kvpi;
+ }
+ }
+ }
+
+ // check for tax-related status
+ QPtrListIterator<GncObject> kvpi (gac->m_kvpList);
+ GncKvp *k;
+ while ((k = static_cast<GncKvp *>(kvpi.current())) != 0) {
+ if (k->key().contains("tax-related") && k->type() == "integer" && k->value() == "1") {
+ acc.setValue ("Tax", "Yes");
+ break;
+ } else {
+ ++kvpi;
+ }
+ }
+
+ // 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().utf8()] = acc.id(); // to link gnucash id to ours for tx posting
+
+ if (gncdebug) qDebug("Gnucash account %s has id of %s, type of %s, parent is %s",
+ gac->id().latin1(), acc.id().data(),
+ KMyMoneyUtils::accountTypeToString(acc.accountType()).latin1(), acc.parentAccountId().data());
+
+ 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().utf8());
+ 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;
+ }
+ for (i = 0; i < gtx->kvpCount(); i++ ) {
+ const GncKvp *slot = gtx->getKvp(i);
+ if (slot->key() == "notes") tx.setMemo(slot->value());
+ }
+ QValueList<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.remove(it);
+ }
+ // memo - set from split - not any more
+ //tx.setMemo(txMemo);
+ 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 coresponding to the gnc id
+ QString kmmAccountId;
+ map_accountIds::Iterator id = m_mapIds.find(gsp->acct().utf8());
+ if (id != m_mapIds.end()) {
+ kmmAccountId = id.data();
+ } 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().latin1(), kmmAccountId.data(), gsp->memo().latin1(), gsp->value().latin1(),
+ // gsp->recon().latin1());
+ // payee id
+ split.setPayeeId (m_txPayeeId.utf8());
+ // reconciled state and date
+ switch (gsp->recon().at(0).latin1()) {
+ 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
+ postMessage ("CC", 4, splitAccount.name().latin1(), m_txDatePosted.toString(Qt::ISODate).latin1());
+ }
+ 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
+ MyMoneySecurity e;
+ MyMoneyMoney price, newPrice(0);
+
+ switch (splitAccount.accountGroup()) {
+ case MyMoneyAccount::Asset:
+ if (splitAccount.accountType() == MyMoneyAccount::Stock) {
+ split.value() == MyMoneyMoney(0) ?
+ split.setAction (MyMoneySplit::ActionAddShares) : // free shares?
+ split.setAction (MyMoneySplit::ActionBuyShares);
+ m_potentialTransfer = false; // ?
+ // add a price history entry
+ e = m_storage->security(splitAccount.currencyId());
+ // newPrice fix supplied by Phil Longstaff
+ price = split.value() / split.shares();
+#define NEW_DENOM 10000
+ if (!split.shares().isZero()) // patch to fix divide by zero?
+ newPrice = MyMoneyMoney ( price.toDouble(), (signed64)NEW_DENOM );
+ if (!newPrice.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 %s, %s date %s",
+ e.name().latin1(), newPrice.toString().latin1(),
+ m_txDatePosted.toString(Qt::ISODate).latin1());
+ m_storage->modifySecurity(e);
+ MyMoneyPrice dealPrice (e.id(), m_txCommodity, m_txDatePosted, newPrice, i18n("Imported Transaction"));
+ m_storage->addPrice (dealPrice);
+ CATCH // stock transfer; treat like free shares?
+ split.setAction (MyMoneySplit::ActionAddShares);
+ delete e;
+ }
+ }
+ } 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:
+ 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().utf8());
+ 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
+ /*QString negativeActionType, positiveActionType;
+ if (!m_splitList.isEmpty()) { // if there are asset splits
+ positiveActionType = MyMoneySplit::ActionDeposit;
+ negativeActionType = MyMoneySplit::ActionWithdrawal;
+ } else { // if there are liability splits
+ positiveActionType = MyMoneySplit::ActionWithdrawal;
+ negativeActionType = MyMoneySplit::ActionDeposit;
+} */
+ 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 = "";
+ QValueList<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.remove(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.utf8());
+ // 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 %s, debit %s, acct %s",
+ gncCreditFormula.latin1(), gncDebitFormula.latin1(), gncAccountId.latin1());
+ validSlotCount++;
+ }
+ // validate numeric, work out sign
+ MyMoneyMoney exFormula (0);
+ 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 ("%s is not numeric", numericTest.latin1());
+ nonNumericFormula = true;
+ }
+ split.setValue (exFormula);
+ xactionCount++;
+ } else {
+ postMessage ("SC", 3, schedName.latin1(), slot->key().latin1(), slot->type().latin1());
+ m_suspectSchedule = true;
+ }
+ }
+ // report this as untranslatable tx
+ if (xactionCount > 1) {
+ postMessage ("SC", 4, schedName.latin1());
+ m_suspectSchedule = true;
+ }
+ if (validSlotCount == 0) {
+ postMessage ("SC", 5, schedName.latin1());
+ m_suspectSchedule = true;
+ }
+ if (nonNumericFormula) {
+ postMessage ("SC", 6, schedName.latin1());
+ m_suspectSchedule = true;
+ }
+ // find the kmm account id coresponding to the gnc id
+ QString kmmAccountId;
+ map_accountIds::Iterator id = m_mapIds.find(gncAccountId.utf8());
+ if (id != m_mapIds.end()) {
+ kmmAccountId = id.data();
+ } 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:
+ m_splitList.append (split); break;
+ case MyMoneyAccount::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
+ QPtrListIterator<GncTransaction> itt (m_templateList);
+ GncTransaction *ttx;
+ while ((ttx = itt.current()) != 0) {
+ // the id to match against is the split:account value in the splits
+ if (static_cast<const GncTemplateSplit *>(ttx->getSplit(0))->acct() == gsc->templId()) break;
+ ++itt;
+ }
+ if (itt == 0) {
+ throw new MYMONEYEXCEPTION (i18n("Can't find template transaction for schedule %1").arg(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::occurenceE occ; // equivalent occurence code
+ MyMoneySchedule::weekendOptionE 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::MoveMonday }, 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}
+ // 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.setOccurence(MyMoneySchedule::OCCUR_ONCE); // FIXME - how to convert
+ } else {
+ // find this interval
+ const GncFreqSpec *fs = gsc->getFreqSpec();
+ if (fs == NULL) {
+ 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") {
+ postMessage ("SC", 1, sc.name().latin1(), frequency.latin1());
+ i = 0; // treat as single occurrence
+ m_suspectSchedule = true;
+ }
+ if (unknownOccurs) {
+ postMessage ("SC", 7, sc.name().latin1());
+ m_suspectSchedule = true;
+ }
+ // set the occurrence interval, weekend option, start date
+ sc.setOccurence (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() != NULL) {
+ postMessage ("SC", 8, sc.name().latin1());
+ m_suspectSchedule = true;
+ }
+ // payment type, options
+ sc.setPaymentType((MyMoneySchedule::paymentTypeE)MyMoneySchedule::STYPE_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() == "y") && (schedEnabled == "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);
+ } else if (actionType == MyMoneySplit::ActionTransfer) {
+ sc.setType((MyMoneySchedule::typeE)MyMoneySchedule::TYPE_TRANSFER);
+ } else {
+ sc.setType((MyMoneySchedule::typeE)MyMoneySchedule::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) {
+ postMessage ("SC", 2, sc.name().latin1());
+ } 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::Iterator it = m_mapIds.begin(); it != m_mapIds.end(); ++it) {
+ if (gncdebug) qDebug("key = %s, value = %s", it.key().data(), it.data().data());
+ }
+ // first step is to implement the users investment option, now we
+ // have all the accounts available
+ QValueList<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..."));
+ QValueList<MyMoneyAccount> list;
+ QValueList<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 %s is a child of the main asset account", (*acc).id().data());
+ } else if ((*acc).parentAccountId() == m_storage->liability().id()) {
+ MyMoneyAccount liabilities = m_storage->liability();
+ m_storage->addAccount(liabilities, (*acc));
+ if (gncdebug) qDebug("Account id %s is a child of the main liability account", (*acc).id().data());
+ } else if ((*acc).parentAccountId() == m_storage->income().id()) {
+ MyMoneyAccount incomes = m_storage->income();
+ m_storage->addAccount(incomes, (*acc));
+ if (gncdebug) qDebug("Account id %s is a child of the main income account", (*acc).id().data());
+ } else if ((*acc).parentAccountId() == m_storage->expense().id()) {
+ MyMoneyAccount expenses = m_storage->expense();
+ m_storage->addAccount(expenses, (*acc));
+ if (gncdebug) qDebug("Account id %s is a child of the main expense account", (*acc).id().data());
+ } else if ((*acc).parentAccountId() == m_storage->equity().id()) {
+ MyMoneyAccount equity = m_storage->equity();
+ m_storage->addAccount(equity, (*acc));
+ if (gncdebug) qDebug("Account id %s is a child of the main equity account", (*acc).id().data());
+ } else if ((*acc).parentAccountId() == m_rootId) {
+ if (gncdebug) qDebug("Account id %s is a child of root", (*acc).id().data());
+ } else {
+ // it is not under one of the main accounts, so find gnucash parent
+ QString parentKey = (*acc).parentAccountId();
+ if (gncdebug) qDebug ("acc %s, parent %s", (*acc).id().data(),
+ (*acc).parentAccountId().data());
+ map_accountIds::Iterator id = m_mapIds.find(parentKey);
+ if (id != m_mapIds.end()) {
+ if (gncdebug) qDebug("Setting account id %s's parent account id to %s",
+ (*acc).id().data(), id.data().data());
+ MyMoneyAccount parent = m_storage->account(id.data());
+ parent = checkConsistency (parent, (*acc));
+ m_storage->addAccount (parent, (*acc));
+ } else {
+ throw new 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.begin(); it != m_currencyCount.end(); ++it) {
+ if (it.data() > maxCount) {
+ maxCount = it.data();
+ mainCurrency = it.key();
+ }
+ }
+
+ if (mainCurrency != "") {
+ /* fix for qt3.3.4?. According to Qt docs, this should return the enum id of the button pressed, and
+ indeed it used to do so. However now it seems to return the index of the button. In this case it doesn't matter,
+ since for Yes, the id is 3 and the index is 0, whereas the No button will return 4 or 1. So we test for either Yes case */
+ /* and now it seems to have changed again, returning 259 for a Yes??? so use KMessagebox */
+ QString question = i18n("Your main currency seems to be %1 (%2); do you want to set this as your base currency?")
+ .arg(mainCurrency).arg(m_storage->currency(mainCurrency.utf8()).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
+ m_ccCount = 0, m_orCount = 0, m_scCount = 0;
+ for (i = 0; i < m_messageList.count(); i++) {
+ if ((*m_messageList.at(i)).source == "CC") m_ccCount++;
+ if ((*m_messageList.at(i)).source == "OR") m_orCount++;
+ if ((*m_messageList.at(i)).source == "SC") m_scCount++;
+ }
+ QValueList<QString> sectionsToReport; // list of sections needing report
+ sectionsToReport.append ("MN"); // always build the main section
+ if (m_ccCount > 0) sectionsToReport.append ("CC");
+ if (m_orCount > 0) sectionsToReport.append ("OR");
+ if (m_scCount > 0) sectionsToReport.append ("SC");
+ // produce the sections in message boxes
+ bool exit = false;
+ for (i = 0; (i < sectionsToReport.count()) && !exit; i++) {
+ QString button0Text = i18n("More");
+ if (i + 1 == sectionsToReport.count())
+ button0Text = i18n("Done"); // last section
+ KGuiItem yesItem(button0Text, QIconSet(), "", "");
+ KGuiItem noItem(i18n("Save Report"), QIconSet(), "", "");
+
+ switch(KMessageBox::questionYesNoCancel(0,
+ buildReportSection (*sectionsToReport.at(i)),
+ PACKAGE,
+ yesItem, noItem)) {
+ case KMessageBox::Yes:
+ break;
+ case KMessageBox::No:
+ exit = writeReportToFile (sectionsToReport);
+ break;
+ default:
+ exit = true;
+ break;
+ }
+ }
+
+ for (i = 0; i < m_suspectList.count(); i++) {
+ MyMoneySchedule sc = m_storage->schedule(m_suspectList[i]);
+ KEditScheduleDlg *s;
+ switch(KMessageBox::warningYesNo(0, i18n("Problems were encountered in converting schedule '%1'.\nDo you want to review or edit it now?").arg(sc.name()), PACKAGE)) {
+ case KMessageBox::Yes:
+ s = new KEditScheduleDlg (sc);
+ // FIXME: connect newCategory to something useful, so that we
+ // can create categories from within the dialog
+ 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 (QString::number(m_commodityCount) + i18n(" commodities (equities)\n"));
+ s.append (QString::number(m_priceCount) + i18n(" prices\n"));
+ s.append (QString::number(m_accountCount) + i18n(" accounts\n"));
+ s.append (QString::number(m_transactionCount) + i18n(" transactions\n"));
+ s.append (QString::number(m_scheduleCount) + i18n(" schedules\n"));
+ s.append ("\n\n");
+ if (m_ccCount == 0) {
+ s.append (i18n("No inconsistencies were detected"));
+ } else {
+ s.append (QString::number(m_ccCount) + i18n(" inconsistencies were detected and corrected\n"));
+ more = true;
+ }
+ if (m_orCount > 0) {
+ s.append ("\n\n");
+ s.append (QString::number(m_orCount) + i18n(" orphan accounts were created\n"));
+ more = true;
+ }
+ if (m_scCount > 0) {
+ s.append ("\n\n");
+ s.append (QString::number(m_scCount) + i18n(" possible schedule problems were noted\n"));
+ 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 { // we need to retrieve the posted messages for this source
+ if (gncdebug) qDebug("Building messages for source %s", source.latin1());
+ unsigned int i, j;
+ for (i = 0; i < m_messageList.count(); i++) {
+ GncMessageArgs *m = m_messageList.at(i);
+ if (m->source == source) {
+ if (gncdebug) qDebug("%s", QString("build text source %1, code %2, argcount %3")
+ .arg(m->source).arg(m->code).arg(m->args.count()).data());
+ QString ss = GncMessages::text (m->source, m->code);
+ // add variable args. the .arg function seems always to replace the
+ // lowest numbered placeholder it finds, so translating messages
+ // with variables in a different order should still work okay (I think...)
+ for (j = 0; j < m->args.count(); j++) ss = ss.arg (*m->args.at(j));
+ s.append (ss + "\n");
+ }
+ }
+ }
+ if (gncdebug) qDebug ("%s", s.latin1());
+ return (static_cast<const QString>(s));
+ PASS
+}
+//************************ writeReportToFile*********************************
+bool MyMoneyGncReader::writeReportToFile (const QValueList<QString>& sectionsToReport) {
+ TRY
+ unsigned int i;
+ QFileDialog* fd = new QFileDialog (0, "Save report as", TRUE);
+ fd->setMode (QFileDialog::AnyFile);
+ if (fd->exec() != QDialog::Accepted) {
+ delete fd;
+ return (false);
+ }
+ QFile reportFile(fd->selectedFile());
+ QFileInfo fi (reportFile);
+ if (!reportFile.open (IO_WriteOnly)) {
+ delete fd;
+ return (false);
+ }
+ QTextStream stream (&reportFile);
+ for (i = 0; i < sectionsToReport.count(); i++) {
+ stream << buildReportSection (*sectionsToReport.at(i)).latin1() << endl;
+ }
+ reportFile.close();
+ delete fd;
+ return (true);
+ PASS
+}
+/****************************************************************************
+ Utility routines
+*****************************************************************************/
+//************************ createPayee ***************************
+
+QString MyMoneyGncReader::createPayee (const QString& gncDescription) {
+ MyMoneyPayee payee;
+ try {
+ payee = m_storage->payeeByName (gncDescription);
+ } catch (MyMoneyException *e) { // payee not found, create one
+ delete e;
+ 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.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.utf8()] = acc.id();
+ postMessage ("OR", 1, acc.name().data());
+ 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 new 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)) {
+ postMessage ("CC", 1, child.name().latin1());
+ return m_storage->asset();
+ }
+ if ((child.accountType() == MyMoneyAccount::Income) &&
+ (parent.accountType() != MyMoneyAccount::Income)) {
+ postMessage ("CC", 2, child.name().latin1());
+ return m_storage->income();
+ }
+ if ((child.accountType() == MyMoneyAccount::Expense) &&
+ (parent.accountType() != MyMoneyAccount::Expense)) {
+ postMessage ("CC", 3, child.name().latin1());
+ 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.utf8()]);
+ MyMoneyAccount parent;
+ QString parentKey = stockAcc.parentAccountId();
+ map_accountIds::Iterator id = m_mapIds.find (parentKey);
+ if (id != m_mapIds.end()) {
+ parent = m_storage->account (id.data());
+ if (parent.accountType() == MyMoneyAccount::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.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 %s as id %s, parent %s", invAcc.name().data(), invAcc.id().data(),
+ invAcc.parentAccountId().data());
+ if (gncdebug) qDebug ("Setting stock %s, id %s, as child of %s", stockAcc.name().data(), stockAcc.id().data(), invAcc.id().data());
+ 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 (PACKAGE,
+ i18n("Enter the investment account name "), QLineEdit::Normal,
+ i18n("My Investments"), &ok);
+ }
+ singleInvAcc.setName (invAccName);
+ singleInvAcc.setAccountType (MyMoneyAccount::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 %s as id %s, parent %s, reparenting stock",
+ singleInvAcc.name().data(), singleInvAcc.id().data(), singleInvAcc.parentAccountId().data());
+ 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;
+ QValueList<MyMoneyAccount> list;
+ QValueList<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 (accList.isEmpty()) qFatal ("No available accounts");
+ bool ok = false;
+ while (!ok) { // keep going till we have a valid investment parent
+ QString invAccName = QInputDialog::getItem (
+ PACKAGE, i18n("Select parent investment account or enter new name. Stock %1").arg(stockAcc.name ()),
+ accList, lastSelected, true, &ok);
+ if (ok) {
+ lastSelected = accList.findIndex (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.setName (invAccName);
+ invAcc.setCurrencyId (QString(""));
+ invAcc.setParentAccountId (m_storage->asset().id());
+ m_storage->addAccount (invAcc);
+ ok = true;
+ }
+ if (invAcc.accountType() == MyMoneyAccount::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?").arg(invAcc.name()),
+ QMessageBox::Question,
+ QMessageBox::Yes | QMessageBox::Default,
+ QMessageBox::No | QMessageBox::Escape,
+ QMessageBox::NoButton);
+ switch (mb.exec()) {
+ case QMessageBox::No :
+ ok = false; break;
+ default:
+ // convert it - but what if it has splits???
+ qFatal ("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?").arg(invAcc.name(), PACKAGE))) {
+ case KMessageBox::Yes:
+ // convert it - but what if it has splits???
+ qFatal ("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
+ qFatal ("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.lower());
+ 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>::Iterator it;
+ for (it = m_mapSources.begin(); it != m_mapSources.end(); it++) {
+ if (it.key() == gncSource) {
+ stock.setValue("kmm-online-source", it.data());
+ m_storage->modifySecurity(stock);
+ return;
+ }
+ }
+ // not found in map, so ask the user
+ 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 ;
+}
+// error and information reporting
+//***************************** Information and error messages *********************
+void MyMoneyGncReader::postMessage (const QString& source, const unsigned int code, const char* arg1) {
+ postMessage (source, code, QStringList(arg1));
+}
+void MyMoneyGncReader::postMessage (const QString& source, const unsigned int code, const char* arg1, const char* arg2) {
+ QStringList argList(arg1);
+ argList.append(arg2);
+ postMessage(source, code, argList);
+}
+void MyMoneyGncReader::postMessage (const QString& source, const unsigned int code, const char* arg1, const char* arg2, const char* arg3) {
+ QStringList argList(arg1);
+ argList.append(arg2);
+ argList.append(arg3);
+ postMessage(source, code, argList);
+}
+void MyMoneyGncReader::postMessage (const QString& source, const unsigned int code, const QStringList& argList) {
+ unsigned int i;
+ GncMessageArgs *m = new GncMessageArgs;
+
+ m->source = source;
+ m->code = code;
+ // get the number of args this message requires
+ const unsigned int argCount = GncMessages::argCount (source, code);
+ if ((gncdebug) && (argCount != argList.count()))
+ qDebug("%s", QString("MyMoneyGncReader::postMessage debug: Message %1, code %2, requires %3 arguments, got %4")
+ .arg(source).arg(code).arg(argCount).arg(argList.count()).data());
+ // store the arguments
+ for (i = 0; i < argCount; i++) {
+ if (i > argList.count()) m->args.append(QString());
+ else m->args.append (argList[i]); //Adds the next argument to the list
+ }
+ m_messageList.append (m);
+ return ;
+}
+//********************************** Message texts **********************************************
+GncMessages::messText GncMessages::texts [] = {
+ {"CC", 1, i18n("An Investment account must be a child of an Asset account\n"
+ "Account %1 will be stored under the main Asset account")},
+ {"CC", 2, i18n("An Income account must be a child of an Income account\n"
+ "Account %1 will be stored under the main Income account")},
+ {"CC", 3, i18n("An Expense account must be a child of an Expense account\n"
+ "Account %1 will be stored under the main Expense account")},
+ {"OR", 1, 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")},
+ {"SC", 1, i18n("Schedule %1 has interval of %2 which is not currently available")},
+ {"SC", 2, i18n("Schedule %1 dropped at user request")},
+ {"SC", 3, i18n("Schedule %1 contains unknown action (key = %2, type = %3)")},
+ {"SC", 4, i18n("Schedule %1 contains multiple actions; only one has been imported")},
+ {"SC", 5, i18n("Schedule %1 contains no valid splits")},
+ {"SC", 6, i18n("Schedule %1 appears to contain a formula. GnuCash formulae are not convertible")},
+ {"SC", 7, i18n("Schedule %1 contains unknown interval specification; please check for correct operation")},
+ {"SC", 8, i18n("Schedule %1 contains a deferred interval specification; please check for correct operation")},
+ {"CC", 4, i18n("Account or Category %1, transaction date %2; split contains invalid value; please check")},
+ {"ZZ", 0, ""} // stopper
+ };
+//
+QString GncMessages::text (const QString source, const unsigned int code) {
+ TRY
+ unsigned int i;
+ for (i = 0; texts[i].source != "ZZ"; i++) {
+ if ((source == texts[i].source) && (code == texts[i].code)) break;
+ }
+ if (texts[i].source == "ZZ") {
+ QString mess = QString().sprintf("Internal error - unknown message - source %s, code %d", source.latin1(), code);
+ throw new MYMONEYEXCEPTION (mess);
+ }
+ return (texts[i].text);
+ PASS
+}
+//
+unsigned int GncMessages::argCount (const QString source, const unsigned int code) {
+ TRY
+ unsigned int i;
+ for (i = 0; texts[i].source != "ZZ"; i++) {
+ if ((source == texts[i].source) && (code == texts[i].code)) break;
+ }
+ if (texts[i].source == "ZZ") {
+ QString mess = QString().sprintf("Internal error - unknown message - source %s, code %d", source.latin1(), code);
+ throw new MYMONEYEXCEPTION (mess);
+ }
+ QRegExp argConst ("%\\d");
+ int offset = 0;
+ unsigned int argCount = 0;
+ while ((offset = argConst.search (texts[i].text, offset)) != -1) {
+ argCount++;
+ offset += 2;
+ }
+ return (argCount);
+ PASS
+}
+#endif // _GNCFILEANON