File indexing completed on 2024-05-12 05:07:35
0001 /* 0002 SPDX-FileCopyrightText: 2005-2019 Thomas Baumgart <tbaumgart@kde.org> 0003 SPDX-FileCopyrightText: 2005-2006 Ace Jones <acejones@users.sourceforge.net> 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "testutilities.h" 0008 0009 #include <QDebug> 0010 #include <QFile> 0011 #include <QList> 0012 #include <QRegularExpression> 0013 #include <QTextStream> 0014 #include <QXmlStreamReader> 0015 #include <QXmlStreamWriter> 0016 0017 #include "mymoneyexception.h" 0018 #include "mymoneyfile.h" 0019 #include "mymoneymoney.h" 0020 #include "mymoneypayee.h" 0021 #include "mymoneyprice.h" 0022 #include "mymoneyreport.h" 0023 #include "mymoneysecurity.h" 0024 #include "mymoneysplit.h" 0025 #include "mymoneystatement.h" 0026 #include "mymoneystoragedump.h" 0027 #include "xmlhelper/xmlstoragehelper.h" 0028 0029 namespace test 0030 { 0031 0032 const MyMoneyMoney moCheckingOpen(0.0); 0033 const MyMoneyMoney moCreditOpen(-0.0); 0034 const MyMoneyMoney moConverterCheckingOpen(1418.0); 0035 const MyMoneyMoney moConverterCreditOpen(-418.0); 0036 const MyMoneyMoney moZero(0.0); 0037 const MyMoneyMoney moSolo(234.12); 0038 const MyMoneyMoney moParent1(88.01); 0039 const MyMoneyMoney moParent2(133.22); 0040 const MyMoneyMoney moParent(moParent1 + moParent2); 0041 const MyMoneyMoney moChild(14.00); 0042 const MyMoneyMoney moThomas(5.11); 0043 const MyMoneyMoney moNoPayee(8944.70); 0044 0045 QString acAsset; 0046 QString acLiability; 0047 QString acExpense; 0048 QString acIncome; 0049 QString acChecking; 0050 QString acTransfer; 0051 QString acCredit; 0052 QString acSolo; 0053 QString acParent; 0054 QString acChild; 0055 QString acSecondChild; 0056 QString acGrandChild1; 0057 QString acGrandChild2; 0058 QString acForeign; 0059 QString acCanChecking; 0060 QString acJpyChecking; 0061 QString acCanCash; 0062 QString acJpyCash; 0063 QString inBank; 0064 QString eqStock1; 0065 QString eqStock2; 0066 QString eqStock3; 0067 QString eqStock4; 0068 QString acInvestment; 0069 QString acStock1; 0070 QString acStock2; 0071 QString acStock3; 0072 QString acStock4; 0073 QString acDividends; 0074 QString acInterest; 0075 QString acFees; 0076 QString acTax; 0077 QString acCash; 0078 QString curBase; 0079 0080 TransactionHelper::TransactionHelper(const QDate& _date, const QString& _action, MyMoneyMoney _value, const QString& _accountid, const QString& _categoryid, const QString& _currencyid, const QString& _payee) 0081 { 0082 // _currencyid is the currency of the transaction, and of the _value 0083 // both the account and category can have their own currency (although the category having 0084 // a foreign currency is not yet supported by the program, the reports will still allow it, 0085 // so it must be tested.) 0086 MyMoneyFile* file = MyMoneyFile::instance(); 0087 bool haspayee = ! _payee.isEmpty(); 0088 MyMoneyPayee payeeTest = file->payeeByName(_payee); 0089 0090 MyMoneyFileTransaction ft; 0091 setPostDate(_date); 0092 0093 QString currencyid = _currencyid; 0094 if (currencyid.isEmpty()) 0095 currencyid = MyMoneyFile::instance()->baseCurrency().id(); 0096 setCommodity(currencyid); 0097 0098 MyMoneyMoney price; 0099 MyMoneySplit splitLeft; 0100 if (haspayee) 0101 splitLeft.setPayeeId(payeeTest.id()); 0102 splitLeft.setAction(_action); 0103 splitLeft.setValue(-_value); 0104 price = MyMoneyFile::instance()->price(currencyid, file->account(_accountid).currencyId(), _date).rate(file->account(_accountid).currencyId()); 0105 splitLeft.setShares(-_value * price); 0106 splitLeft.setAccountId(_accountid); 0107 addSplit(splitLeft); 0108 0109 MyMoneySplit splitRight; 0110 if (haspayee) 0111 splitRight.setPayeeId(payeeTest.id()); 0112 splitRight.setAction(_action); 0113 splitRight.setValue(_value); 0114 price = MyMoneyFile::instance()->price(currencyid, file->account(_categoryid).currencyId(), _date).rate(file->account(_categoryid).currencyId()); 0115 splitRight.setShares(_value * price); 0116 splitRight.setAccountId(_categoryid); 0117 addSplit(splitRight); 0118 0119 MyMoneyFile::instance()->addTransaction(*this); 0120 ft.commit(); 0121 } 0122 0123 TransactionHelper::~TransactionHelper() 0124 { 0125 MyMoneyFileTransaction ft; 0126 try { 0127 MyMoneyFile::instance()->removeTransaction(*this); 0128 ft.commit(); 0129 } catch (const MyMoneyException &e) { 0130 qDebug() << e.what(); 0131 } 0132 } 0133 0134 void TransactionHelper::update() 0135 { 0136 MyMoneyFileTransaction ft; 0137 MyMoneyFile::instance()->modifyTransaction(*this); 0138 ft.commit(); 0139 } 0140 0141 InvTransactionHelper::InvTransactionHelper(const QDate& _date, const QString& _action, MyMoneyMoney _shares, MyMoneyMoney _price, const QString& _stockaccountid, const QString& _transferid, const QString& _categoryid, MyMoneyMoney _fee) 0142 { 0143 init(_date, _action, _shares, _price, _fee, _stockaccountid, _transferid, _categoryid); 0144 } 0145 0146 void InvTransactionHelper::init(const QDate& _date, const QString& _action, MyMoneyMoney _shares, MyMoneyMoney _price, MyMoneyMoney _fee, const QString& _stockaccountid, const QString& _transferid, const QString& _categoryid) 0147 { 0148 MyMoneyFile* file = MyMoneyFile::instance(); 0149 MyMoneyAccount stockaccount = file->account(_stockaccountid); 0150 MyMoneyMoney value = _shares * _price; 0151 0152 setPostDate(_date); 0153 0154 setCommodity("USD"); 0155 MyMoneySplit s1; 0156 s1.setValue(value); 0157 s1.setAccountId(_stockaccountid); 0158 0159 if (_action == MyMoneySplit::actionName(eMyMoney::Split::Action::ReinvestDividend)) { 0160 s1.setShares(_shares); 0161 s1.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::ReinvestDividend)); 0162 0163 MyMoneySplit s2; 0164 s2.setAccountId(_categoryid); 0165 s2.setShares(-value); 0166 s2.setValue(-value); 0167 addSplit(s2); 0168 } else if (_action == MyMoneySplit::actionName(eMyMoney::Split::Action::Dividend) || _action == MyMoneySplit::actionName(eMyMoney::Split::Action::Yield)) { 0169 s1.setAccountId(_categoryid); 0170 s1.setShares(-value); 0171 s1.setValue(-value); 0172 0173 // Split 2 will be the zero-amount investment split that serves to 0174 // mark this transaction as a cash dividend and note which stock account 0175 // it belongs to. 0176 MyMoneySplit s2; 0177 s2.setValue(MyMoneyMoney()); 0178 s2.setShares(MyMoneyMoney()); 0179 s2.setAction(_action); 0180 s2.setAccountId(_stockaccountid); 0181 addSplit(s2); 0182 0183 MyMoneySplit s3; 0184 s3.setAccountId(_transferid); 0185 s3.setShares(value); 0186 s3.setValue(value); 0187 addSplit(s3); 0188 } else if (_action == MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares)) { 0189 s1.setShares(_shares); 0190 s1.setValue(value); 0191 s1.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares)); 0192 0193 MyMoneySplit s3; 0194 s3.setAccountId(_transferid); 0195 s3.setShares(-value - _fee); 0196 s3.setValue(-value - _fee); 0197 addSplit(s3); 0198 0199 if (!_categoryid.isEmpty() && !_fee.isZero()) { 0200 MyMoneySplit s2; 0201 s2.setAccountId(_categoryid); 0202 s2.setValue(_fee); 0203 s2.setShares(_fee); 0204 addSplit(s2); 0205 } 0206 } else if (_action == MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) { 0207 s1.setShares(_shares.abs()); 0208 s1.setValue(MyMoneyMoney()); 0209 s1.setPrice(MyMoneyMoney()); 0210 } 0211 addSplit(s1); 0212 0213 //qDebug() << "created transaction, now adding..."; 0214 0215 MyMoneyFileTransaction ft; 0216 file->addTransaction(*this); 0217 0218 //qDebug() << "updating price..."; 0219 0220 // update the price, while we're here 0221 if (_action != MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) { 0222 QString stockid = stockaccount.currencyId(); 0223 QString basecurrencyid = file->baseCurrency().id(); 0224 MyMoneyPrice price = file->price(stockid, basecurrencyid, _date, true); 0225 if (!price.isValid()) { 0226 MyMoneyPrice newprice(stockid, basecurrencyid, _date, _price, "test"); 0227 file->addPrice(newprice); 0228 } 0229 } 0230 ft.commit(); 0231 //qDebug() << "successfully added " << id(); 0232 } 0233 0234 QString makeAccount(const QString& id, const QString& _name, eMyMoney::Account::Type _type, MyMoneyMoney _balance, const QDate& _open, const QString& _parent, QString _currency, bool _taxReport, bool _openingBalance) 0235 { 0236 int maxTries = 1000; 0237 QString cid; 0238 do { 0239 if (!cid.isEmpty()) { 0240 auto acc = MyMoneyFile::instance()->account(cid); 0241 MyMoneyFileTransaction ft; 0242 MyMoneyFile::instance()->removeAccount(acc); 0243 ft.commit(); 0244 } 0245 cid = makeAccount(_name, _type, _balance, _open, _parent, _currency, _taxReport, _openingBalance); 0246 } while(maxTries-- && id != cid); 0247 if (maxTries <= 0) 0248 throw MYMONEYEXCEPTION_CSTRING("Account could not be prepared"); 0249 return cid; 0250 } 0251 0252 QString makeAccount(const QString& _name, eMyMoney::Account::Type _type, MyMoneyMoney _balance, const QDate& _open, const QString& _parent, QString _currency, bool _taxReport, bool _openingBalance) 0253 { 0254 MyMoneyAccount info; 0255 MyMoneyFileTransaction ft; 0256 0257 info.setName(_name); 0258 info.setAccountType(_type); 0259 info.setOpeningDate(_open); 0260 if (!_currency.isEmpty()) 0261 info.setCurrencyId(_currency); 0262 else 0263 info.setCurrencyId(MyMoneyFile::instance()->baseCurrency().id()); 0264 0265 if (_taxReport) 0266 info.setValue("Tax", "Yes"); 0267 0268 if (_openingBalance) 0269 info.setValue("OpeningBalanceAccount", "Yes"); 0270 0271 MyMoneyAccount parent = MyMoneyFile::instance()->account(_parent); 0272 MyMoneyFile::instance()->addAccount(info, parent); 0273 // create the opening balance transaction if any 0274 if (!_balance.isZero()) { 0275 MyMoneySecurity sec = MyMoneyFile::instance()->currency(info.currencyId()); 0276 MyMoneyFile::instance()->openingBalanceAccount(sec); 0277 MyMoneyFile::instance()->createOpeningBalanceTransaction(info, _balance); 0278 } 0279 ft.commit(); 0280 0281 return info.id(); 0282 } 0283 0284 void makePrice(const QString& _currencyid, const QDate& _date, const MyMoneyMoney& _price) 0285 { 0286 MyMoneyFileTransaction ft; 0287 MyMoneyFile* file = MyMoneyFile::instance(); 0288 MyMoneySecurity curr = file->currency(_currencyid); 0289 MyMoneyPrice price(_currencyid, file->baseCurrency().id(), _date, _price, "test"); 0290 file->addPrice(price); 0291 ft.commit(); 0292 } 0293 0294 QString makeEquity(const QString& _name, const QString& _symbol) 0295 { 0296 MyMoneySecurity equity; 0297 MyMoneyFileTransaction ft; 0298 0299 equity.setName(_name); 0300 equity.setTradingSymbol(_symbol); 0301 equity.setSmallestAccountFraction(1000); 0302 equity.setSecurityType(eMyMoney::Security::Type::None/*MyMoneyEquity::ETYPE_STOCK*/); 0303 MyMoneyFile::instance()->addSecurity(equity); 0304 ft.commit(); 0305 0306 return equity.id(); 0307 } 0308 0309 void makeEquityPrice(const QString& _id, const QDate& _date, const MyMoneyMoney& _price) 0310 { 0311 MyMoneyFile* file = MyMoneyFile::instance(); 0312 MyMoneyFileTransaction ft; 0313 QString basecurrencyid = file->baseCurrency().id(); 0314 MyMoneyPrice price = file->price(_id, basecurrencyid, _date, true); 0315 if (!price.isValid()) { 0316 MyMoneyPrice newprice(_id, basecurrencyid, _date, _price, "test"); 0317 file->addPrice(newprice); 0318 } 0319 ft.commit(); 0320 } 0321 0322 QString makeBaseCurrency(const MyMoneySecurity& base) 0323 { 0324 auto file = MyMoneyFile::instance(); 0325 MyMoneyFileTransaction ft; 0326 try { 0327 file->currency(base.id()); 0328 } catch (const MyMoneyException&) { 0329 file->addCurrency(base); 0330 } 0331 file->setBaseCurrency(base); 0332 ft.commit(); 0333 return base.id(); 0334 } 0335 0336 void writeRCFtoXMLDoc(const MyMoneyReport& filter, QXmlStreamWriter* writer) 0337 { 0338 writer->writeDTD(QLatin1String("<!DOCTYPE KMYMONEY-FILE>")); 0339 writer->writeStartElement(QLatin1String("KMYMONEY-FILE")); 0340 writer->writeStartElement(QLatin1String("REPORTS")); 0341 MyMoneyXmlHelper::writeReport(filter, writer); 0342 writer->writeEndElement(); 0343 writer->writeEndElement(); 0344 } 0345 0346 void writeRCFtoXML(const MyMoneyReport& filter, const QString& _filename) 0347 { 0348 static unsigned filenum = 1; 0349 QString filename = _filename; 0350 if (filename.isEmpty()) { 0351 filename = QString("report-%1%2.xml").arg(QString::number(filenum).rightJustified(2, '0')); 0352 ++filenum; 0353 } 0354 0355 QFile g(filename); 0356 g.open(QIODevice::WriteOnly); 0357 0358 QXmlStreamWriter writer(&g); 0359 writeRCFtoXMLDoc(filter, &writer); 0360 0361 g.close(); 0362 } 0363 0364 bool readRCFfromXMLDoc(QList<MyMoneyReport>& list, QXmlStreamReader* reader) 0365 { 0366 bool result = false; 0367 0368 while (reader->readNextStartElement()) { 0369 if (reader->name() == QLatin1String("KMYMONEY-FILE")) { 0370 while (reader->readNextStartElement()) { 0371 if (reader->name() == QLatin1String("REPORTS")) { 0372 while (reader->readNextStartElement()) { 0373 if (reader->name() == QLatin1String("REPORT")) { 0374 result = true; 0375 const auto filter = MyMoneyXmlHelper::readReport(reader); 0376 if (reader->hasError()) 0377 qDebug() << reader->errorString(); 0378 list += filter; 0379 } else { 0380 reader->skipCurrentElement(); 0381 } 0382 } 0383 } else { 0384 reader->skipCurrentElement(); 0385 } 0386 } 0387 } else { 0388 reader->skipCurrentElement(); 0389 } 0390 } 0391 0392 return result; 0393 } 0394 0395 bool readRCFfromXML(QList<MyMoneyReport>& list, const QString& filename) 0396 { 0397 int result = false; 0398 QFile f(filename); 0399 f.open(QIODevice::ReadOnly); 0400 0401 QXmlStreamReader reader; 0402 reader.setDevice(&f); 0403 0404 result = readRCFfromXMLDoc(list, &reader); 0405 0406 return result; 0407 0408 } 0409 0410 void XMLandback(MyMoneyReport& filter) 0411 { 0412 // this function writes the filter to XML, and then reads 0413 // it back from XML overwriting the original filter; 0414 // in all cases, the result should be the same if the read 0415 // & write methods are working correctly. 0416 0417 QString xml; 0418 QXmlStreamWriter writer(&xml); 0419 0420 writeRCFtoXMLDoc(filter, &writer); 0421 QXmlStreamReader reader(xml); 0422 QList<MyMoneyReport> list; 0423 if (readRCFfromXMLDoc(list, &reader) && !list.isEmpty()) 0424 filter = list[0]; 0425 else 0426 throw MYMONEYEXCEPTION_CSTRING("Failed to load report from XML"); 0427 } 0428 0429 MyMoneyMoney searchHTML(const QString& _html, const QString& _search) 0430 { 0431 Q_UNUSED(_html) 0432 const QRegularExpression re(QStringLiteral("%1[<>/td]*([\\-.0-9,]*)").arg(_search)); 0433 const auto html(re.match(_html)); 0434 if (html.hasMatch()) { 0435 auto found = html.captured(1); 0436 found.remove(','); 0437 0438 return MyMoneyMoney(found.toDouble()); 0439 } 0440 return MyMoneyMoney(); 0441 } 0442 0443 } // end namespace test