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