File indexing completed on 2024-05-12 05:06:45

0001 /*
0002     SPDX-FileCopyrightText: 2000-2002 Michael Edwardes <mte@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2001-2019 Thomas Baumgart <tbaumgart@kde.org>
0004     SPDX-FileCopyrightText: 2001 Felix Rodriguez <frodriguez@users.sourceforge.net>
0005     SPDX-FileCopyrightText: 2003 Kevin Tambascio <ktambascio@users.sourceforge.net>
0006     SPDX-FileCopyrightText: 2004-2005 Ace Jones <acejones@users.sourceforge.net>
0007     SPDX-FileCopyrightText: 2006 Darren Gould <darren_gould@gmx.de>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "mymoneytransaction.h"
0012 #include "mymoneytransaction_p.h"
0013 
0014 // ----------------------------------------------------------------------------
0015 // QT Includes
0016 
0017 #include <QStringList>
0018 #include <QMap>
0019 
0020 // ----------------------------------------------------------------------------
0021 // Project Includes
0022 
0023 #include "mymoneyutils.h"
0024 #include "mymoneymoney.h"
0025 #include "mymoneyexception.h"
0026 #include "mymoneyenums.h"
0027 
0028 MyMoneyTransaction::MyMoneyTransaction() :
0029     MyMoneyObject(*new MyMoneyTransactionPrivate)
0030 {
0031 }
0032 
0033 MyMoneyTransaction::MyMoneyTransaction(const QString &id) :
0034     MyMoneyObject(*new MyMoneyTransactionPrivate, id)
0035 {
0036 }
0037 
0038 MyMoneyTransaction::MyMoneyTransaction(const MyMoneyTransaction& other) :
0039     MyMoneyObject(*new MyMoneyTransactionPrivate(*other.d_func()), other.id()),
0040     MyMoneyKeyValueContainer(other)
0041 {
0042 }
0043 
0044 MyMoneyTransaction::MyMoneyTransaction(const QString& id, const MyMoneyTransaction& other) :
0045     MyMoneyObject(*new MyMoneyTransactionPrivate(*other.d_func()), id),
0046     MyMoneyKeyValueContainer(other)
0047 {
0048     Q_D(MyMoneyTransaction);
0049     if (d->m_entryDate == QDate())
0050         d->m_entryDate = QDate::currentDate();
0051 
0052     for (auto& split : d->m_splits)
0053         split.setTransactionId(id);
0054 }
0055 
0056 MyMoneyTransaction::~MyMoneyTransaction()
0057 {
0058 }
0059 
0060 QDate MyMoneyTransaction::entryDate() const
0061 {
0062     Q_D(const MyMoneyTransaction);
0063     return d->m_entryDate;
0064 }
0065 
0066 void MyMoneyTransaction::setEntryDate(const QDate& date)
0067 {
0068     Q_D(MyMoneyTransaction);
0069     d->m_entryDate = date;
0070 }
0071 
0072 QDate MyMoneyTransaction::postDate() const
0073 {
0074     Q_D(const MyMoneyTransaction);
0075     return d->m_postDate;
0076 }
0077 
0078 void MyMoneyTransaction::setPostDate(const QDate& date)
0079 {
0080     Q_D(MyMoneyTransaction);
0081     d->m_postDate = date;
0082 }
0083 
0084 QString MyMoneyTransaction::memo() const
0085 {
0086     Q_D(const MyMoneyTransaction);
0087     return d->m_memo;
0088 }
0089 
0090 void MyMoneyTransaction::setMemo(const QString& memo)
0091 {
0092     Q_D(MyMoneyTransaction);
0093     d->m_memo = memo;
0094 }
0095 
0096 QList<MyMoneySplit> MyMoneyTransaction::splits() const
0097 {
0098     Q_D(const MyMoneyTransaction);
0099     return d->m_splits;
0100 }
0101 
0102 QList<MyMoneySplit>& MyMoneyTransaction::splits()
0103 {
0104     Q_D(MyMoneyTransaction);
0105     return d->m_splits;
0106 }
0107 
0108 MyMoneySplit MyMoneyTransaction::firstSplit() const
0109 {
0110     Q_D(const MyMoneyTransaction);
0111     return d->m_splits.first();
0112 }
0113 
0114 uint MyMoneyTransaction::splitCount() const
0115 {
0116     Q_D(const MyMoneyTransaction);
0117     return d->m_splits.count();
0118 }
0119 
0120 uint MyMoneyTransaction::splitCountWithValue() const
0121 {
0122     uint rc = 0;
0123     Q_D(const MyMoneyTransaction);
0124     const auto splitCount = d->m_splits.count();
0125     for (int split = 0; split < splitCount; ++split) {
0126         rc += (d->m_splits[split].value().isZero() ? 0 : 1);
0127     }
0128     return rc;
0129 }
0130 
0131 QString MyMoneyTransaction::commodity() const
0132 {
0133     Q_D(const MyMoneyTransaction);
0134     return d->m_commodity;
0135 }
0136 
0137 void MyMoneyTransaction::setCommodity(const QString& commodityId)
0138 {
0139     Q_D(MyMoneyTransaction);
0140     d->m_commodity = commodityId;
0141     d->clearReferences();
0142 }
0143 
0144 QString MyMoneyTransaction::bankID() const
0145 {
0146     Q_D(const MyMoneyTransaction);
0147     return d->m_bankID;
0148 }
0149 
0150 void MyMoneyTransaction::setBankID(const QString& bankID)
0151 {
0152     Q_D(MyMoneyTransaction);
0153     d->m_bankID = bankID;
0154 }
0155 
0156 bool MyMoneyTransaction::operator == (const MyMoneyTransaction& right) const
0157 {
0158     Q_D(const MyMoneyTransaction);
0159     auto d2 = static_cast<const MyMoneyTransactionPrivate *>(right.d_func());
0160     return (MyMoneyObject::operator==(right)
0161             && MyMoneyKeyValueContainer::operator==(right) //
0162             && (d->m_commodity == d2->m_commodity) //
0163             && ((d->m_memo.length() == 0 && d2->m_memo.length() == 0) || (d->m_memo == d2->m_memo))  //
0164             && (d->m_splits == d2->m_splits) //
0165             && (d->m_entryDate == d2->m_entryDate) //
0166             && (d->m_postDate == d2->m_postDate));
0167 }
0168 
0169 bool MyMoneyTransaction::operator != (const MyMoneyTransaction& r) const
0170 {
0171     return !(*this == r);
0172 }
0173 
0174 bool MyMoneyTransaction::operator< (const MyMoneyTransaction& r) const
0175 {
0176     return postDate() < r.postDate();
0177 }
0178 
0179 bool MyMoneyTransaction::operator<= (const MyMoneyTransaction& r) const
0180 {
0181     return postDate() <= r.postDate();
0182 }
0183 
0184 bool MyMoneyTransaction::operator> (const MyMoneyTransaction& r) const
0185 {
0186     return postDate() > r.postDate();
0187 }
0188 
0189 bool MyMoneyTransaction::accountReferenced(const QString& id) const
0190 {
0191     Q_D(const MyMoneyTransaction);
0192 
0193     for (const auto& split : d->m_splits) {
0194         if (split.accountId() == id)
0195             return true;
0196     }
0197     return false;
0198 }
0199 
0200 void MyMoneyTransaction::addSplit(MyMoneySplit &split)
0201 {
0202     if (!split.id().isEmpty())
0203         throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot add split with assigned id '%1' to transaction %2").arg(split.id(), id()));
0204 
0205     if (split.accountId().isEmpty())
0206         throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot add split that does not contain an account reference to transaction %1").arg(id()));
0207 
0208     Q_D(MyMoneyTransaction);
0209     MyMoneySplit newSplit(d->nextSplitID(), split);
0210     split = newSplit;
0211     split.setTransactionId(id());
0212     d->m_splits.append(split);
0213     d->clearReferences();
0214 }
0215 
0216 void MyMoneyTransaction::modifySplit(const MyMoneySplit& split)
0217 {
0218 // This is the other version which allows having more splits referencing
0219 // the same account.
0220     if (split.accountId().isEmpty())
0221         throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot modify split that does not contain an account reference in transaction %1").arg(id()));
0222 
0223     Q_D(MyMoneyTransaction);
0224     for (auto& it_split : d->m_splits) {
0225         if (split.id() == it_split.id()) {
0226             it_split = split;
0227             d->clearReferences();
0228             return;
0229         }
0230     }
0231     throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid split id '%1' in transaction %2").arg(split.id(), id()));
0232 }
0233 
0234 void MyMoneyTransaction::removeSplit(const MyMoneySplit& split)
0235 {
0236     Q_D(MyMoneyTransaction);
0237     for (int end = d->m_splits.size(), i = 0; i < end; ++i) {
0238         if (split.id() == d->m_splits.at(i).id()) {
0239             d->m_splits.removeAt(i);
0240             d->clearReferences();
0241             return;
0242         }
0243     }
0244 
0245     throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid split id '%1' in transaction %2").arg(split.id(), id()));
0246 }
0247 
0248 void MyMoneyTransaction::removeSplits()
0249 {
0250     Q_D(MyMoneyTransaction);
0251     d->m_splits.clear();
0252     d->clearReferences();
0253 }
0254 
0255 MyMoneySplit MyMoneyTransaction::splitByPayee(const QString& payeeId) const
0256 {
0257     Q_D(const MyMoneyTransaction);
0258     for (const auto& split : d->m_splits) {
0259         if (split.payeeId() == payeeId)
0260             return split;
0261     }
0262     throw MYMONEYEXCEPTION(QString::fromLatin1("Split not found for payee '%1' in transaction %2").arg(payeeId, id()));
0263 }
0264 
0265 MyMoneySplit MyMoneyTransaction::splitByAccount(const QString& accountId, const bool match) const
0266 {
0267     Q_D(const MyMoneyTransaction);
0268     for (const auto& split : d->m_splits) {
0269         if ((match == true && split.accountId() == accountId) ||
0270                 (match == false && split.accountId() != accountId))
0271             return split;
0272     }
0273     throw MYMONEYEXCEPTION(QString::fromLatin1("Split not found for account %1%2 in transaction %3").arg(match ? "" : "!", accountId, id()));
0274 }
0275 
0276 MyMoneySplit MyMoneyTransaction::splitByAccount(const QStringList& accountIds, const bool match) const
0277 {
0278     Q_D(const MyMoneyTransaction);
0279     for (const auto& split : d->m_splits) {
0280         if ((match == true && accountIds.contains(split.accountId())) ||
0281                 (match == false && !accountIds.contains(split.accountId())))
0282             return split;
0283     }
0284     throw MYMONEYEXCEPTION(
0285         QString::fromLatin1("Split not found for account  %1%2...%3 in transaction %4").arg(match ? "" : "!", accountIds.front(), accountIds.back(), id()));
0286 }
0287 
0288 MyMoneySplit MyMoneyTransaction::splitById(const QString& splitId) const
0289 {
0290     Q_D(const MyMoneyTransaction);
0291     for (const auto& split : d->m_splits) {
0292         if (split.id() == splitId)
0293             return split;
0294     }
0295     throw MYMONEYEXCEPTION(QString::fromLatin1("Split not found for id '%1' in transaction %2").arg(splitId, id()));
0296 }
0297 
0298 QString MyMoneyTransaction::firstSplitID()
0299 {
0300     QString id;
0301     id = 'S' + id.setNum(1).rightJustified(MyMoneyTransactionPrivate::SPLIT_ID_SIZE, '0');
0302     return id;
0303 }
0304 
0305 MyMoneyMoney MyMoneyTransaction::splitSum() const
0306 {
0307     MyMoneyMoney result;
0308 
0309     Q_D(const MyMoneyTransaction);
0310     for (const auto& split : d->m_splits)
0311         result += split.value();
0312     return result;
0313 }
0314 
0315 void MyMoneyTransaction::reverse()
0316 {
0317     Q_D(MyMoneyTransaction);
0318     for (MyMoneySplit& split : d->m_splits) {
0319         split.negateValue();
0320         split.negateShares();
0321     }
0322 }
0323 
0324 bool MyMoneyTransaction::isLoanPayment() const
0325 {
0326     try {
0327 
0328         Q_D(const MyMoneyTransaction);
0329         for (const auto& split : d->m_splits) {
0330             if (split.isAmortizationSplit())
0331                 return true;
0332         }
0333     } catch (const MyMoneyException &) {
0334     }
0335     return false;
0336 }
0337 
0338 MyMoneySplit MyMoneyTransaction::amortizationSplit() const
0339 {
0340     static MyMoneySplit nullSplit;
0341 
0342     Q_D(const MyMoneyTransaction);
0343     for (const auto& split : d->m_splits) {
0344         if (split.isAmortizationSplit() && split.isAutoCalc())
0345             return split;
0346     }
0347     return nullSplit;
0348 }
0349 
0350 MyMoneySplit MyMoneyTransaction::interestSplit() const
0351 {
0352     static MyMoneySplit nullSplit;
0353 
0354     Q_D(const MyMoneyTransaction);
0355     for (const auto& split : d->m_splits) {
0356         if (split.isInterestSplit() && split.isAutoCalc())
0357             return split;
0358     }
0359     return nullSplit;
0360 }
0361 
0362 unsigned long MyMoneyTransaction::hash(const QString& txt, unsigned long h)
0363 {
0364     unsigned long g;
0365 
0366     for (int i = 0; i < txt.length(); ++i) {
0367         unsigned short uc = txt[i].unicode();
0368         for (unsigned j = 0; j < 2; ++j) {
0369             unsigned char c = uc & 0xff;
0370             // if either the cell or the row of the Unicode char is 0, stop processing
0371             if (!c)
0372                 break;
0373             h = (h << 4) + c;
0374             if ((g = (h & 0xf0000000))) {
0375                 h = h ^(g >> 24);
0376                 h = h ^ g;
0377             }
0378             uc >>= 8;
0379         }
0380     }
0381     return h;
0382 }
0383 
0384 bool MyMoneyTransaction::isStockSplit() const
0385 {
0386     Q_D(const MyMoneyTransaction);
0387     return (d->m_splits.count() == 1 && d->m_splits.first().action() == MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares));
0388 }
0389 
0390 bool MyMoneyTransaction::isImported() const
0391 {
0392     return value("Imported").toLower() == QString("true");
0393 }
0394 
0395 void MyMoneyTransaction::setImported(bool state)
0396 {
0397     if (state)
0398         setValue("Imported", "true");
0399     else
0400         deletePair("Imported");
0401 }
0402 
0403 bool MyMoneyTransaction::hasAutoCalcSplit() const
0404 {
0405     Q_D(const MyMoneyTransaction);
0406 
0407     for (const auto& split : d->m_splits)
0408         if (split.isAutoCalc())
0409             return true;
0410     return false;
0411 }
0412 
0413 QString MyMoneyTransaction::accountSignature(bool includeSplitCount) const
0414 {
0415     Q_D(const MyMoneyTransaction);
0416     QMap<QString, int> accountList;
0417     for (const auto& split : d->m_splits)
0418         accountList[split.accountId()] += 1;
0419 
0420     QMap<QString, int>::const_iterator it_a;
0421     QString rc;
0422     for (it_a = accountList.constBegin(); it_a != accountList.constEnd(); ++it_a) {
0423         if (it_a != accountList.constBegin())
0424             rc += '-';
0425         rc += it_a.key();
0426         if (includeSplitCount)
0427             rc += QString("*%1").arg(*it_a);
0428     }
0429     return rc;
0430 }
0431 
0432 QString MyMoneyTransaction::uniqueSortKey() const
0433 {
0434     Q_D(const MyMoneyTransaction);
0435     return uniqueSortKey(postDate(), d->m_id);
0436 }
0437 
0438 QString MyMoneyTransaction::uniqueSortKey(const QDate& date, const QString& id)
0439 {
0440     QString year, month, day;
0441     year = year.setNum(date.year()).rightJustified(MyMoneyTransactionPrivate::YEAR_SIZE, '0');
0442     month = month.setNum(date.month()).rightJustified(MyMoneyTransactionPrivate::MONTH_SIZE, '0');
0443     day = day.setNum(date.day()).rightJustified(MyMoneyTransactionPrivate::DAY_SIZE, '0');
0444     const auto key = QString::fromLatin1("%1-%2-%3-%4").arg(year, month, day, id);
0445     return key;
0446 }
0447 
0448 bool MyMoneyTransaction::replaceId(const QString& newId, const QString& oldId)
0449 {
0450     auto changed = false;
0451     Q_D(MyMoneyTransaction);
0452     for (MyMoneySplit& split : d->m_splits)
0453         changed |= split.replaceId(newId, oldId);
0454 
0455     return changed;
0456 }