You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2464 lines
97 KiB
2464 lines
97 KiB
/***************************************************************************
|
|
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 <tqfile.h>
|
|
#include <tqmap.h>
|
|
#include <tqobject.h>
|
|
#include <tqfiledialog.h>
|
|
#include <tqinputdialog.h>
|
|
#include <tqdatetime.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 <tqtextedit.h>
|
|
#define i18n TQObject::tr
|
|
#define TRY
|
|
#define CATCH
|
|
#define PASS
|
|
#define MYMONEYEXCEPTION TQString
|
|
#define MyMoneyException TQString
|
|
#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 TQString& elName, const TQXmlAttributes& 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"))) {
|
|
TQString 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 TQString& elName, const TQXmlAttributes& 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 TQString &elName, const TQXmlAttributes& 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
|
|
TQString 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
|
|
TQString GncObject::hide (TQString 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 TQMap<TQString, TQString> anonPayees; // to check for duplicate payee names
|
|
static TQMap<TQString, TQString> anonStocks; // for reference to equities
|
|
|
|
TQString result (data);
|
|
TQMap<TQString, TQString>::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);
|
|
}
|
|
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);
|
|
}
|
|
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 TQString 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 TQString 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 TQString (""));
|
|
}
|
|
|
|
GncDate::~GncDate() {}
|
|
//*************************************GncCmdtySpec***************************************
|
|
GncCmdtySpec::GncCmdtySpec () {
|
|
m_subElementListCount = 0;
|
|
static const TQString 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 TQString (""));
|
|
}
|
|
|
|
GncCmdtySpec::~GncCmdtySpec () {}
|
|
|
|
TQString GncCmdtySpec::hide(TQString 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 TQString subEls[] = {"slot"}; // kvp's may be nested
|
|
m_subElementList = subEls;
|
|
m_dataElementListCount = END_Kvp_DELS;
|
|
static const TQString 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 TQString (""));
|
|
m_kvpList.setAutoDelete (true);
|
|
}
|
|
|
|
GncKvp::~GncKvp () {}
|
|
|
|
void GncKvp::dataEl (const TQXmlAttributes& 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 TQString ("")); // only 1 data item
|
|
}
|
|
|
|
GncCountData::~GncCountData () {}
|
|
|
|
void GncCountData::initiate (const TQString&, const TQXmlAttributes& 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 TQString 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 TQString (""));
|
|
}
|
|
|
|
GncCommodity::~GncCommodity () {}
|
|
|
|
void GncCommodity::terminate() {
|
|
TRY
|
|
pMain->convertCommodity (this);
|
|
return ;
|
|
PASS
|
|
}
|
|
//************* GncPrice********************************************
|
|
GncPrice::GncPrice () {
|
|
static const TQString subEls[] = {"price:commodity", "price:currency", "price:time"};
|
|
m_subElementList = subEls;
|
|
m_subElementListCount = END_Price_SELS;
|
|
m_dataElementListCount = END_Price_DELS;
|
|
static const TQString 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 TQString (""));
|
|
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 TQString subEls[] = {"act:commodity", "slot", "act:lots"};
|
|
m_subElementList = subEls;
|
|
m_dataElementListCount = END_Account_DELS;
|
|
static const TQString 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 TQString (""));
|
|
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 TQString subEls[] = {"trn:currency", "trn:date-posted", "trn:date-entered",
|
|
"trn:split", "slot"};
|
|
m_subElementList = subEls;
|
|
m_dataElementListCount = END_Transaction_DELS;
|
|
static const TQString 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 TQString (""));
|
|
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 TQString subEls[] = {"split:reconcile-date"};
|
|
m_subElementList = subEls;
|
|
m_dataElementListCount = END_Split_DELS;
|
|
static const TQString 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 TQString (""));
|
|
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 TQString subEls[] = {"slot"};
|
|
m_subElementList = subEls;
|
|
m_dataElementListCount = END_TemplateSplit_DELS;
|
|
static const TQString 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 TQString (""));
|
|
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 TQString subEls[] = {"sx:start", "sx:last", "sx:end", "gnc:freqspec", "gnc:recurrence","sx:deferredInstance"};
|
|
m_subElementList = subEls;
|
|
m_dataElementListCount = END_Schedule_DELS;
|
|
static const TQString 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 TQString (""));
|
|
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 TQString subEls[] = {"gnc:freqspec"};
|
|
m_subElementList = subEls;
|
|
m_dataElementListCount = END_FreqSpec_DELS;
|
|
static const TQString 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 TQString (""));
|
|
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 TQString subEls[] = {"recurrence:start"};
|
|
m_subElementList = subEls;
|
|
m_dataElementListCount = END_Recurrence_DELS;
|
|
static const TQString 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 TQString (""));
|
|
}
|
|
|
|
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 ;
|
|
}
|
|
|
|
TQString 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 (TQIODevice* pDevice) {
|
|
m_source = new TQXmlInputSource (pDevice); // set up the TQt XML reader
|
|
m_reader = new TQXmlSimpleReader;
|
|
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 TQString&, const TQString&, const TQString& elName ,
|
|
const TQXmlAttributes& elAttrs) {
|
|
try {
|
|
if (pMain->gncdebug) qDebug ("XML start - %s", elName.latin1());
|
|
#ifdef _GNCFILEANON
|
|
int i;
|
|
TQString 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 TQString&, const TQString&, const TQString&elName ) {
|
|
try {
|
|
if (pMain->xmldebug) qDebug ("XML end - %s", elName.latin1());
|
|
#ifdef _GNCFILEANON
|
|
TQString 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 TQString &data) {
|
|
if (pMain->xmldebug) qDebug ("XML Data received - %d bytes", data.length());
|
|
TQString 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
|
|
TQString anonData = m_co->getData ();
|
|
if (anonData.isEmpty()) anonData = pData;
|
|
// there must be a TQt standard way of doing the following but I can't ... find it
|
|
anonData.replace ('<', "<");
|
|
anonData.replace ('>', ">");
|
|
anonData.replace ('&', "&");
|
|
pMain->oStream << anonData; // write original data
|
|
lastType = 1;
|
|
#endif // _GNCFILEANON
|
|
}
|
|
return (true);
|
|
}
|
|
|
|
bool XmlReader::endDocument() {
|
|
#ifdef _GNCFILEANON
|
|
pMain->oStream << endl << endl;
|
|
pMain->oStream << "<!-- 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 TQString 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(TQIODevice* pDevice, IMyMoneySerialize* storage) {
|
|
|
|
TQ_CHECK_PTR (pDevice);
|
|
TQ_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(TQString in, TQString out) {
|
|
TQFile pDevice (in);
|
|
if (!pDevice.open (IO_ReadOnly)) qFatal ("Can't open input file");
|
|
TQFile 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 <tqapplication.h>
|
|
int main (int argc, char ** argv) {
|
|
TQApplication a (argc, argv);
|
|
MyMoneyGncReader m;
|
|
TQString inFile, outFile;
|
|
|
|
if (argc > 0) inFile = a.argv()[1];
|
|
if (argc > 1) outFile = a.argv()[2];
|
|
if (inFile.isEmpty()) {
|
|
inFile = TQFileDialog::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 (TQTime::currentTime().second()); // seed randomizer for anonymize
|
|
m_fileHideFactor = 0.0;
|
|
while (m_fileHideFactor == 0.0) {
|
|
m_fileHideFactor = TQInputDialog::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) {
|
|
TQ_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) {
|
|
TQ_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) {
|
|
TQ_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());
|
|
|
|
TQDate currentDate = TQDate::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" "ETQUITY" "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 ("ETQUITY" == 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
|
|
TQString 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
|
|
TQPtrListIterator<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
|
|
TQPtrListIterator<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) {
|
|
TQ_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());
|
|
}
|
|
TQValueList<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) {
|
|
TQ_CHECK_PTR (gsp);
|
|
MyMoneySplit split;
|
|
MyMoneyAccount splitAccount;
|
|
// find the kmm account id coresponding to the gnc id
|
|
TQString 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(), TQString(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(),
|
|
TQString(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 TQString.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 TQString& schedName, const GncTransaction *gtx) {
|
|
|
|
TQ_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
|
|
/*TQString 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!
|
|
TQString txMemo = "";
|
|
TQValueList<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 TQString& schedName, const GncTemplateSplit *gsp) {
|
|
TQ_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;
|
|
TQString 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;
|
|
TQString 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);
|
|
TQString 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) == TQValidator::Acceptable) {
|
|
switch (crdr) {
|
|
case 'C':
|
|
exFormula = TQString ("-" + 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
|
|
TQString 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
|
|
TQ_CHECK_PTR (gsc);
|
|
MyMoneySchedule sc;
|
|
MyMoneyTransaction tx;
|
|
m_suspectSchedule = false;
|
|
TQDate startDate, nextDate, lastDate, endDate; // for date calculations
|
|
TQDate today = TQDate::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
|
|
TQPtrListIterator<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 {
|
|
TQString 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_TQUARTERLY, 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?
|
|
};
|
|
|
|
TQString frequency = "unknown"; // set default to unknown frequency
|
|
bool unknownOccurs = false; // may have zero, or more than one frequency/recurrence spec
|
|
TQString schedEnabled;
|
|
if (gsc->version() == "2.0.0") {
|
|
if (gsc->m_vpRecurrence.count() != 1) {
|
|
unknownOccurs = true;
|
|
} else {
|
|
const GncRecurrence *gre = gsc->m_vpRecurrence.first();
|
|
//qDebug (TQString("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() == TQDate()) {
|
|
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() == TQDate()) && (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(TQString("%1 and %2").arg(gsc->autoCreate()).arg(schedEnabled));
|
|
sc.setAutoEnter ((gsc->autoCreate() == "y") && (schedEnabled == "y"));
|
|
//qDebug(TQString("autoEnter set to %1").arg(sc.autoEnter()));
|
|
// type
|
|
TQString 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
|
|
TQValueList<TQString>::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..."));
|
|
TQValueList<MyMoneyAccount> list;
|
|
TQValueList<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
|
|
TQString 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
|
|
TQString mainCurrency = "";
|
|
unsigned int maxCount = 0;
|
|
TQMap<TQString, 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 TQt 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 */
|
|
TQString 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++;
|
|
}
|
|
TQValueList<TQString> 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++) {
|
|
TQString button0Text = i18n("More");
|
|
if (i + 1 == sectionsToReport.count())
|
|
button0Text = i18n("Done"); // last section
|
|
KGuiItem yesItem(button0Text, TQIconSet(), "", "");
|
|
KGuiItem noItem(i18n("Save Report"), TQIconSet(), "", "");
|
|
|
|
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************************************
|
|
TQString MyMoneyGncReader::buildReportSection (const TQString& source) {
|
|
TRY
|
|
TQString s = "";
|
|
bool more = false;
|
|
if (source == "MN") {
|
|
s.append (i18n("Found:\n\n"));
|
|
s.append (TQString::number(m_commodityCount) + i18n(" commodities (equities)\n"));
|
|
s.append (TQString::number(m_priceCount) + i18n(" prices\n"));
|
|
s.append (TQString::number(m_accountCount) + i18n(" accounts\n"));
|
|
s.append (TQString::number(m_transactionCount) + i18n(" transactions\n"));
|
|
s.append (TQString::number(m_scheduleCount) + i18n(" schedules\n"));
|
|
s.append ("\n\n");
|
|
if (m_ccCount == 0) {
|
|
s.append (i18n("No inconsistencies were detected"));
|
|
} else {
|
|
s.append (TQString::number(m_ccCount) + i18n(" inconsistencies were detected and corrected\n"));
|
|
more = true;
|
|
}
|
|
if (m_orCount > 0) {
|
|
s.append ("\n\n");
|
|
s.append (TQString::number(m_orCount) + i18n(" orphan accounts were created\n"));
|
|
more = true;
|
|
}
|
|
if (m_scCount > 0) {
|
|
s.append ("\n\n");
|
|
s.append (TQString::number(m_scCount) + i18n(" possible schedule problems were noted\n"));
|
|
more = true;
|
|
}
|
|
TQString unsupported ("");
|
|
TQString 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", TQString("build text source %1, code %2, argcount %3")
|
|
.arg(m->source).arg(m->code).arg(m->args.count()).data());
|
|
TQString 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 TQString>(s));
|
|
PASS
|
|
}
|
|
//************************ writeReportToFile*********************************
|
|
bool MyMoneyGncReader::writeReportToFile (const TQValueList<TQString>& sectionsToReport) {
|
|
TRY
|
|
unsigned int i;
|
|
TQFileDialog* fd = new TQFileDialog (0, "Save report as", TRUE);
|
|
fd->setMode (TQFileDialog::AnyFile);
|
|
if (fd->exec() != TQDialog::Accepted) {
|
|
delete fd;
|
|
return (false);
|
|
}
|
|
TQFile reportFile(fd->selectedFile());
|
|
TQFileInfo fi (reportFile);
|
|
if (!reportFile.open (IO_WriteOnly)) {
|
|
delete fd;
|
|
return (false);
|
|
}
|
|
TQTextStream 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 ***************************
|
|
|
|
TQString MyMoneyGncReader::createPayee (const TQString& 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 *******************************
|
|
TQString MyMoneyGncReader::createOrphanAccount (const TQString& gncName) {
|
|
MyMoneyAccount acc;
|
|
|
|
acc.setName ("orphan_" + gncName);
|
|
acc.setDescription (i18n("Orphan created from unknown gnucash account"));
|
|
|
|
TQDate today = TQDate::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 (TQString("OR"), 1, acc.name().ascii());
|
|
return (acc.id());
|
|
}
|
|
//****************************** incrDate *********************************************
|
|
TQDate MyMoneyGncReader::incrDate (TQDate 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"));
|
|
TQDate r = TQDate(); 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 (TQString 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;
|
|
TQString 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 (TQString("")); // 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 TQString singleInvAccId = "";
|
|
MyMoneyAccount singleInvAcc;
|
|
bool ok = false;
|
|
if (singleInvAccId.isEmpty()) { // if the account has not yet been created
|
|
TQString invAccName;
|
|
while (!ok) {
|
|
invAccName = TQInputDialog::getText (PACKAGE,
|
|
i18n("Enter the investment account name "), TQLineEdit::Normal,
|
|
i18n("My Investments"), &ok);
|
|
}
|
|
singleInvAcc.setName (invAccName);
|
|
singleInvAcc.setAccountType (MyMoneyAccount::Investment);
|
|
singleInvAcc.setCurrencyId (TQString(""));
|
|
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);
|
|
TQStringList accList;
|
|
TQValueList<MyMoneyAccount> list;
|
|
TQValueList<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
|
|
TQString invAccName = TQInputDialog::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 (TQString(""));
|
|
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
|
|
TQMessageBox mb (PACKAGE,
|
|
i18n ("%1 is not an Investment Account. Do you wish to make it one?").arg(invAcc.name()),
|
|
TQMessageBox::Question,
|
|
TQMessageBox::Yes | TQMessageBox::Default,
|
|
TQMessageBox::No | TQMessageBox::Escape,
|
|
TQMessageBox::NoButton);
|
|
switch (mb.exec()) {
|
|
case TQMessageBox::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, TQString 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.)
|
|
TQMap<TQString, TQString>::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();
|
|
TQString 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 TQString&)) {
|
|
m_progressCallback = callback; return ;
|
|
}
|
|
//************************** signalProgress *******************************
|
|
void MyMoneyGncReader::signalProgress(int current, int total, const TQString& msg) {
|
|
if (m_progressCallback != 0)
|
|
(*m_progressCallback)(current, total, msg);
|
|
return ;
|
|
}
|
|
// error and information reporting
|
|
//***************************** Information and error messages *********************
|
|
void MyMoneyGncReader::postMessage (const TQString& source, const unsigned int code, const char* arg1) {
|
|
postMessage (source, code, TQStringList(arg1));
|
|
}
|
|
void MyMoneyGncReader::postMessage (const TQString& source, const unsigned int code, const char* arg1, const char* arg2) {
|
|
TQStringList argList(arg1);
|
|
argList.append(arg2);
|
|
postMessage(source, code, argList);
|
|
}
|
|
void MyMoneyGncReader::postMessage (const TQString& source, const unsigned int code, const char* arg1, const char* arg2, const char* arg3) {
|
|
TQStringList argList(arg1);
|
|
argList.append(arg2);
|
|
argList.append(arg3);
|
|
postMessage(source, code, argList);
|
|
}
|
|
void MyMoneyGncReader::postMessage (const TQString& source, const unsigned int code, const TQStringList& 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", TQString("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(TQString());
|
|
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
|
|
};
|
|
//
|
|
TQString GncMessages::text (const TQString 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") {
|
|
TQString mess = TQString().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 TQString 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") {
|
|
TQString mess = TQString().sprintf("Internal error - unknown message - source %s, code %d", source.latin1(), code);
|
|
throw new MYMONEYEXCEPTION (mess);
|
|
}
|
|
TQRegExp 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
|