File indexing completed on 2024-05-12 16:42:41
0001 /* 0002 SPDX-FileCopyrightText: 2004-2006 Ace Jones <acejones@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2005-2018 Thomas Baumgart <tbaumgart@kde.org> 0004 SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "mymoneystatement.h" 0009 0010 // ---------------------------------------------------------------------------- 0011 // QT Includes 0012 0013 #include <QStringList> 0014 #include <QFile> 0015 #include <QTextStream> 0016 #include <QList> 0017 #include <QDomProcessingInstruction> 0018 #include <QDomElement> 0019 #include <QDomDocument> 0020 0021 // ---------------------------------------------------------------------------- 0022 // Project Includes 0023 0024 namespace eMyMoney { 0025 namespace Statement { 0026 enum class Element { 0027 KMMStatement, 0028 Statement, 0029 Transaction, 0030 Split, 0031 Price, 0032 Security, 0033 }; 0034 uint qHash(const Element key, uint seed) { 0035 return ::qHash(static_cast<uint>(key), seed); 0036 } 0037 0038 enum class Attribute { 0039 Name, 0040 Symbol, 0041 ID, 0042 Version, 0043 AccountName, 0044 AccountNumber, 0045 RoutingNumber, 0046 Currency, 0047 BeginDate, 0048 EndDate, 0049 ClosingBalance, 0050 Type, 0051 AccountID, 0052 SkipCategoryMatching, 0053 DatePosted, 0054 Payee, 0055 Memo, 0056 Number, 0057 Amount, 0058 BankID, 0059 Reconcile, 0060 Action, 0061 Shares, 0062 Security, 0063 BrokerageAccount, 0064 Category, 0065 }; 0066 uint qHash(const Attribute key, uint seed) { 0067 return ::qHash(static_cast<uint>(key), seed); 0068 } 0069 } 0070 } 0071 0072 using namespace eMyMoney; 0073 0074 const QHash<Statement::Type, QString> txAccountType { 0075 {Statement::Type::None, QStringLiteral("none")}, 0076 {Statement::Type::Checkings, QStringLiteral("checkings")}, 0077 {Statement::Type::Savings, QStringLiteral("savings")}, 0078 {Statement::Type::Investment, QStringLiteral("investment")}, 0079 {Statement::Type::CreditCard, QStringLiteral("creditcard")}, 0080 {Statement::Type::Invalid, QStringLiteral("invalid")}, 0081 }; 0082 0083 const QHash<Transaction::Action, QString> txAction { 0084 {Transaction::Action::None, QStringLiteral("none")}, 0085 {Transaction::Action::Buy, QStringLiteral("buy")}, 0086 {Transaction::Action::Sell, QStringLiteral("sell")}, 0087 {Transaction::Action::ReinvestDividend, QStringLiteral("reinvestdividend")}, 0088 {Transaction::Action::CashDividend, QStringLiteral("cashdividend")}, 0089 {Transaction::Action::Shrsin, QStringLiteral("add")}, 0090 {Transaction::Action::Shrsout, QStringLiteral("remove")}, 0091 {Transaction::Action::Stksplit, QStringLiteral("stocksplit")}, 0092 {Transaction::Action::Fees, QStringLiteral("fees")}, 0093 {Transaction::Action::Interest, QStringLiteral("interest")}, 0094 {Transaction::Action::Invalid, QStringLiteral("invalid")}, 0095 }; 0096 0097 QString getElName(const Statement::Element el) 0098 { 0099 static const QHash<Statement::Element, QString> elNames { 0100 {Statement::Element::KMMStatement, QStringLiteral("KMYMONEY-STATEMENT")}, 0101 {Statement::Element::Statement, QStringLiteral("STATEMENT")}, 0102 {Statement::Element::Transaction, QStringLiteral("TRANSACTION")}, 0103 {Statement::Element::Split, QStringLiteral("SPLIT")}, 0104 {Statement::Element::Price, QStringLiteral("PRICE")}, 0105 {Statement::Element::Security, QStringLiteral("SECURITY")}, 0106 }; 0107 return elNames[el]; 0108 } 0109 0110 QString getAttrName(const Statement::Attribute attr) 0111 { 0112 static const QHash<Statement::Attribute, QString> attrNames { 0113 {Statement::Attribute::Name, QStringLiteral("name")}, 0114 {Statement::Attribute::Symbol, QStringLiteral("symbol")}, 0115 {Statement::Attribute::ID, QStringLiteral("id")}, 0116 {Statement::Attribute::Version, QStringLiteral("version")}, 0117 {Statement::Attribute::AccountName, QStringLiteral("accountname")}, 0118 {Statement::Attribute::AccountNumber, QStringLiteral("accountnumber")}, 0119 {Statement::Attribute::RoutingNumber, QStringLiteral("routingnumber")}, 0120 {Statement::Attribute::Currency, QStringLiteral("currency")}, 0121 {Statement::Attribute::BeginDate, QStringLiteral("begindate")}, 0122 {Statement::Attribute::EndDate, QStringLiteral("enddate")}, 0123 {Statement::Attribute::ClosingBalance, QStringLiteral("closingbalance")}, 0124 {Statement::Attribute::Type, QStringLiteral("type")}, 0125 {Statement::Attribute::AccountID, QStringLiteral("accountid")}, 0126 {Statement::Attribute::SkipCategoryMatching, QStringLiteral("skipCategoryMatching")}, 0127 {Statement::Attribute::DatePosted, QStringLiteral("dateposted")}, 0128 {Statement::Attribute::Payee, QStringLiteral("payee")}, 0129 {Statement::Attribute::Memo, QStringLiteral("memo")}, 0130 {Statement::Attribute::Number, QStringLiteral("number")}, 0131 {Statement::Attribute::Amount, QStringLiteral("amount")}, 0132 {Statement::Attribute::BankID, QStringLiteral("bankid")}, 0133 {Statement::Attribute::Reconcile, QStringLiteral("reconcile")}, 0134 {Statement::Attribute::Action, QStringLiteral("action")}, 0135 {Statement::Attribute::Shares, QStringLiteral("shares")}, 0136 {Statement::Attribute::Security, QStringLiteral("security")}, 0137 {Statement::Attribute::BrokerageAccount, QStringLiteral("brokerageaccount")}, 0138 {Statement::Attribute::Category, QStringLiteral("version")}, 0139 }; 0140 return attrNames[attr]; 0141 } 0142 0143 void MyMoneyStatement::write(QDomElement& _root, QDomDocument* _doc) const 0144 { 0145 QDomElement e = _doc->createElement(getElName(Statement::Element::Statement)); 0146 _root.appendChild(e); 0147 0148 e.setAttribute(getAttrName(Statement::Attribute::Version), QStringLiteral("1.1")); 0149 e.setAttribute(getAttrName(Statement::Attribute::AccountName), m_strAccountName); 0150 e.setAttribute(getAttrName(Statement::Attribute::AccountNumber), m_strAccountNumber); 0151 e.setAttribute(getAttrName(Statement::Attribute::RoutingNumber), m_strRoutingNumber); 0152 e.setAttribute(getAttrName(Statement::Attribute::Currency), m_strCurrency); 0153 e.setAttribute(getAttrName(Statement::Attribute::BeginDate), m_dateBegin.toString(Qt::ISODate)); 0154 e.setAttribute(getAttrName(Statement::Attribute::EndDate), m_dateEnd.toString(Qt::ISODate)); 0155 e.setAttribute(getAttrName(Statement::Attribute::ClosingBalance), m_closingBalance.toString()); 0156 e.setAttribute(getAttrName(Statement::Attribute::Type), txAccountType[m_eType]); 0157 e.setAttribute(getAttrName(Statement::Attribute::AccountID), m_accountId); 0158 e.setAttribute(getAttrName(Statement::Attribute::SkipCategoryMatching), m_skipCategoryMatching); 0159 0160 // iterate over transactions, and add each one 0161 foreach (const auto tansaction, m_listTransactions) { 0162 auto p = _doc->createElement(getElName(Statement::Element::Transaction)); 0163 p.setAttribute(getAttrName(Statement::Attribute::DatePosted), tansaction.m_datePosted.toString(Qt::ISODate)); 0164 p.setAttribute(getAttrName(Statement::Attribute::Payee), tansaction.m_strPayee); 0165 p.setAttribute(getAttrName(Statement::Attribute::Memo), tansaction.m_strMemo); 0166 p.setAttribute(getAttrName(Statement::Attribute::Number), tansaction.m_strNumber); 0167 p.setAttribute(getAttrName(Statement::Attribute::Amount), tansaction.m_amount.toString()); 0168 p.setAttribute(getAttrName(Statement::Attribute::BankID), tansaction.m_strBankID); 0169 p.setAttribute(getAttrName(Statement::Attribute::Reconcile), (int)tansaction.m_reconcile); 0170 p.setAttribute(getAttrName(Statement::Attribute::Action), txAction[tansaction.m_eAction]); 0171 0172 if (m_eType == eMyMoney::Statement::Type::Investment) { 0173 p.setAttribute(getAttrName(Statement::Attribute::Shares), tansaction.m_shares.toString()); 0174 p.setAttribute(getAttrName(Statement::Attribute::Security), tansaction.m_strSecurity); 0175 p.setAttribute(getAttrName(Statement::Attribute::BrokerageAccount), tansaction.m_strBrokerageAccount); 0176 } 0177 0178 // add all the splits we know of (might be empty) 0179 foreach (const auto split, tansaction.m_listSplits) { 0180 auto el = _doc->createElement(getElName(Statement::Element::Split)); 0181 el.setAttribute(getAttrName(Statement::Attribute::AccountID), split.m_accountId); 0182 el.setAttribute(getAttrName(Statement::Attribute::Amount), split.m_amount.toString()); 0183 el.setAttribute(getAttrName(Statement::Attribute::Reconcile), (int)split.m_reconcile); 0184 el.setAttribute(getAttrName(Statement::Attribute::Category), split.m_strCategoryName); 0185 el.setAttribute(getAttrName(Statement::Attribute::Memo), split.m_strMemo); 0186 el.setAttribute(getAttrName(Statement::Attribute::Reconcile), (int)split.m_reconcile); 0187 p.appendChild(el); 0188 } 0189 e.appendChild(p); 0190 } 0191 0192 // iterate over prices, and add each one 0193 foreach (const auto price, m_listPrices) { 0194 auto p = _doc->createElement(getElName(Statement::Element::Price)); 0195 p.setAttribute(getAttrName(Statement::Attribute::DatePosted), price.m_date.toString(Qt::ISODate)); 0196 p.setAttribute(getAttrName(Statement::Attribute::Security), price.m_strSecurity); 0197 p.setAttribute(getAttrName(Statement::Attribute::Amount), price.m_amount.toString()); 0198 0199 e.appendChild(p); 0200 } 0201 0202 // iterate over securities, and add each one 0203 foreach (const auto security, m_listSecurities) { 0204 auto p = _doc->createElement(getElName(Statement::Element::Security)); 0205 p.setAttribute(getAttrName(Statement::Attribute::Name), security.m_strName); 0206 p.setAttribute(getAttrName(Statement::Attribute::Symbol), security.m_strSymbol); 0207 p.setAttribute(getAttrName(Statement::Attribute::ID), security.m_strId); 0208 0209 e.appendChild(p); 0210 } 0211 } 0212 0213 bool MyMoneyStatement::read(const QDomElement& _e) 0214 { 0215 bool result = false; 0216 0217 if (_e.tagName() == getElName(Statement::Element::Statement)) { 0218 result = true; 0219 0220 m_strAccountName = _e.attribute(getAttrName(Statement::Attribute::AccountName)); 0221 m_strAccountNumber = _e.attribute(getAttrName(Statement::Attribute::AccountNumber)); 0222 m_strRoutingNumber = _e.attribute(getAttrName(Statement::Attribute::RoutingNumber)); 0223 m_strCurrency = _e.attribute(getAttrName(Statement::Attribute::Currency)); 0224 m_dateBegin = QDate::fromString(_e.attribute(getAttrName(Statement::Attribute::BeginDate)), Qt::ISODate); 0225 m_dateEnd = QDate::fromString(_e.attribute(getAttrName(Statement::Attribute::EndDate)), Qt::ISODate); 0226 m_closingBalance = MyMoneyMoney(_e.attribute(getAttrName(Statement::Attribute::ClosingBalance))); 0227 m_accountId = _e.attribute(getAttrName(Statement::Attribute::AccountID)); 0228 m_skipCategoryMatching = _e.attribute(getAttrName(Statement::Attribute::SkipCategoryMatching)).isEmpty(); 0229 0230 auto txt = _e.attribute(getAttrName(Statement::Attribute::Type), txAccountType[Statement::Type::Checkings]); 0231 m_eType = txAccountType.key(txt, m_eType); 0232 0233 QDomNode child = _e.firstChild(); 0234 while (!child.isNull() && child.isElement()) { 0235 QDomElement c = child.toElement(); 0236 0237 if (c.tagName() == getElName(Statement::Element::Transaction)) { 0238 MyMoneyStatement::Transaction t; 0239 0240 t.m_datePosted = QDate::fromString(c.attribute(getAttrName(Statement::Attribute::DatePosted)), Qt::ISODate); 0241 t.m_amount = MyMoneyMoney(c.attribute(getAttrName(Statement::Attribute::Amount))); 0242 t.m_strMemo = c.attribute(getAttrName(Statement::Attribute::Memo)); 0243 t.m_strNumber = c.attribute(getAttrName(Statement::Attribute::Number)); 0244 t.m_strPayee = c.attribute(getAttrName(Statement::Attribute::Payee)); 0245 t.m_strBankID = c.attribute(getAttrName(Statement::Attribute::BankID)); 0246 t.m_reconcile = static_cast<eMyMoney::Split::State>(c.attribute(getAttrName(Statement::Attribute::Reconcile)).toInt()); 0247 0248 txt = c.attribute(getAttrName(Statement::Attribute::Action), txAction[eMyMoney::Transaction::Action::Buy]); 0249 t.m_eAction = txAction.key(txt, t.m_eAction); 0250 0251 if (m_eType == eMyMoney::Statement::Type::Investment) { 0252 t.m_shares = MyMoneyMoney(c.attribute(getAttrName(Statement::Attribute::Shares))); 0253 t.m_strSecurity = c.attribute(getAttrName(Statement::Attribute::Security)); 0254 t.m_strBrokerageAccount = c.attribute(getAttrName(Statement::Attribute::BrokerageAccount)); 0255 } 0256 0257 // process splits (if any) 0258 child = c.firstChild(); 0259 while (!child.isNull() && child.isElement()) { 0260 c = child.toElement(); 0261 if (c.tagName() == getElName(Statement::Element::Split)) { 0262 MyMoneyStatement::Split s; 0263 s.m_accountId = c.attribute(getAttrName(Statement::Attribute::AccountID)); 0264 s.m_amount = MyMoneyMoney(c.attribute(getAttrName(Statement::Attribute::Amount))); 0265 s.m_reconcile = static_cast<eMyMoney::Split::State>(c.attribute(getAttrName(Statement::Attribute::Reconcile)).toInt()); 0266 s.m_strCategoryName = c.attribute(getAttrName(Statement::Attribute::Category)); 0267 s.m_strMemo = c.attribute(getAttrName(Statement::Attribute::Memo)); 0268 t.m_listSplits += s; 0269 } 0270 child = child.nextSibling(); 0271 } 0272 m_listTransactions += t; 0273 } else if (c.tagName() == getElName(Statement::Element::Price)) { 0274 MyMoneyStatement::Price p; 0275 0276 p.m_date = QDate::fromString(c.attribute(getAttrName(Statement::Attribute::DatePosted)), Qt::ISODate); 0277 p.m_strSecurity = c.attribute(getAttrName(Statement::Attribute::Security)); 0278 p.m_amount = MyMoneyMoney(c.attribute(getAttrName(Statement::Attribute::Amount))); 0279 0280 m_listPrices += p; 0281 } else if (c.tagName() == getElName(Statement::Element::Security)) { 0282 MyMoneyStatement::Security s; 0283 0284 s.m_strName = c.attribute(getAttrName(Statement::Attribute::Name)); 0285 s.m_strSymbol = c.attribute(getAttrName(Statement::Attribute::Symbol)); 0286 s.m_strId = c.attribute(getAttrName(Statement::Attribute::ID)); 0287 0288 m_listSecurities += s; 0289 } 0290 child = child.nextSibling(); 0291 } 0292 } 0293 0294 return result; 0295 } 0296 0297 bool MyMoneyStatement::isStatementFile(const QString& _filename) 0298 { 0299 // filename is considered a statement file if it contains 0300 // the tag "<KMYMONEY2-STATEMENT>" in the first 20 lines. 0301 bool result = false; 0302 0303 QFile f(_filename); 0304 if (f.open(QIODevice::ReadOnly)) { 0305 QTextStream ts(&f); 0306 0307 auto lineCount = 20; 0308 while (!ts.atEnd() && !result && lineCount != 0) { 0309 if (ts.readLine().contains(QLatin1String("<KMYMONEY-STATEMENT>"), Qt::CaseInsensitive)) 0310 result = true; 0311 --lineCount; 0312 } 0313 f.close(); 0314 } 0315 0316 return result; 0317 } 0318 0319 void MyMoneyStatement::writeXMLFile(const MyMoneyStatement& _s, const QString& _filename) 0320 { 0321 static unsigned filenum = 1; 0322 auto filename = _filename; 0323 if (filename.isEmpty()) { 0324 filename = QString::fromLatin1("statement-%1%2.xml").arg((filenum < 10) ? QStringLiteral("0") : QString()).arg(filenum); 0325 filenum++; 0326 } 0327 0328 auto doc = new QDomDocument(getElName(Statement::Element::KMMStatement)); 0329 Q_CHECK_PTR(doc); 0330 0331 //writeStatementtoXMLDoc(_s,doc); 0332 QDomProcessingInstruction instruct = doc->createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"utf-8\"")); 0333 doc->appendChild(instruct); 0334 auto eroot = doc->createElement(getElName(Statement::Element::KMMStatement)); 0335 doc->appendChild(eroot); 0336 _s.write(eroot, doc); 0337 0338 QFile g(filename); 0339 if (g.open(QIODevice::WriteOnly)) { 0340 QTextStream stream(&g); 0341 stream.setCodec("UTF-8"); 0342 stream << doc->toString(); 0343 g.close(); 0344 } 0345 0346 delete doc; 0347 } 0348 0349 bool MyMoneyStatement::readXMLFile(MyMoneyStatement& _s, const QString& _filename) 0350 { 0351 bool result = false; 0352 QFile f(_filename); 0353 f.open(QIODevice::ReadOnly); 0354 QDomDocument* doc = new QDomDocument; 0355 if (doc->setContent(&f, false)) { 0356 QDomElement rootElement = doc->documentElement(); 0357 if (!rootElement.isNull()) { 0358 QDomNode child = rootElement.firstChild(); 0359 while (!child.isNull() && child.isElement()) { 0360 result = true; 0361 QDomElement childElement = child.toElement(); 0362 _s.read(childElement); 0363 0364 child = child.nextSibling(); 0365 } 0366 } 0367 } 0368 delete doc; 0369 0370 return result; 0371 } 0372 0373 QDate MyMoneyStatement::statementEndDate() const 0374 { 0375 if (m_dateEnd.isValid()) 0376 return m_dateEnd; 0377 0378 // if we don't have an end date listed in the 0379 // statement, we take the last postdate we 0380 // find that is not in the future and use it 0381 // as the end date of the statement. 0382 QDate postDate; 0383 for(auto& t : m_listTransactions) { 0384 if ((t.m_datePosted > postDate) && (t.m_datePosted <= QDate::currentDate())) { 0385 postDate = t.m_datePosted; 0386 } 0387 } 0388 return postDate; 0389 }