File indexing completed on 2024-05-12 16:41:58
0001 /* 0002 KMyMoney transaction importing module - base class for searching for a matching transaction 0003 0004 SPDX-FileCopyrightText: 2012 Lukasz Maszczynski <lukasz@maszczynski.net> 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "transactionmatchfinder.h" 0009 0010 #include <QDebug> 0011 #include <QDate> 0012 0013 #include <KLocalizedString> 0014 0015 #include "mymoneymoney.h" 0016 #include "mymoneyaccount.h" 0017 #include "mymoneyfile.h" 0018 #include "mymoneypayee.h" 0019 #include "mymoneyexception.h" 0020 0021 TransactionMatchFinder::TransactionMatchFinder(int _matchWindow) : 0022 m_matchWindow(_matchWindow), 0023 matchResult(MatchNotFound) 0024 { 0025 } 0026 0027 TransactionMatchFinder::~TransactionMatchFinder() 0028 { 0029 } 0030 0031 TransactionMatchFinder::MatchResult TransactionMatchFinder::findMatch(const MyMoneyTransaction& transactionToMatch, const MyMoneySplit& splitToMatch) 0032 { 0033 importedTransaction = transactionToMatch; 0034 m_importedSplit = splitToMatch; 0035 matchResult = MatchNotFound; 0036 matchedTransaction.reset(); 0037 matchedSplit.reset(); 0038 matchedSchedule.reset(); 0039 0040 QString date = importedTransaction.postDate().toString(Qt::ISODate); 0041 QString payeeName = MyMoneyFile::instance()->payee(m_importedSplit.payeeId()).name(); 0042 QString amount = m_importedSplit.shares().formatMoney("", 2); 0043 QString account = MyMoneyFile::instance()->account(m_importedSplit.accountId()).name(); 0044 qDebug() << "Looking for a match with transaction: " << date << "," << payeeName << "," << amount 0045 << "(referenced account: " << account << ")"; 0046 0047 createListOfMatchCandidates(); 0048 findMatchInMatchCandidatesList(); 0049 return matchResult; 0050 } 0051 0052 MyMoneySplit TransactionMatchFinder::getMatchedSplit() const 0053 { 0054 if (matchedSplit.isNull()) { 0055 throw MYMONEYEXCEPTION(QString::fromLatin1("Internal error - no matching splits")); 0056 } 0057 0058 return *matchedSplit; 0059 } 0060 0061 MyMoneyTransaction TransactionMatchFinder::getMatchedTransaction() const 0062 { 0063 if (matchedTransaction.isNull()) { 0064 throw MYMONEYEXCEPTION(QString::fromLatin1("Internal error - no matching transactions")); 0065 } 0066 0067 return *matchedTransaction; 0068 } 0069 0070 MyMoneySchedule TransactionMatchFinder::getMatchedSchedule() const 0071 { 0072 if (matchedSchedule.isNull()) { 0073 throw MYMONEYEXCEPTION(QString::fromLatin1("Internal error - no matching schedules")); 0074 } 0075 0076 return *matchedSchedule; 0077 } 0078 0079 bool TransactionMatchFinder::splitsAreDuplicates(const MyMoneySplit& split1, const MyMoneySplit& split2, int amountVariation) const 0080 { 0081 return (splitsAmountsMatch(split1, split2, amountVariation) && splitsBankIdsDuplicated(split1, split2)); 0082 } 0083 0084 bool TransactionMatchFinder::splitsMatch(const MyMoneySplit& importedSplit, const MyMoneySplit& existingSplit, int amountVariation) const 0085 { 0086 return (splitsAccountsMatch(importedSplit, existingSplit) // 0087 && splitsBankIdsMatch(importedSplit, existingSplit) // 0088 && splitsAmountsMatch(importedSplit, existingSplit, amountVariation) // 0089 && splitsPayeesMatchOrEmpty(importedSplit, existingSplit) // 0090 && !existingSplit.isMatched()); 0091 } 0092 0093 bool TransactionMatchFinder::splitsAccountsMatch(const MyMoneySplit & split1, const MyMoneySplit & split2) const 0094 { 0095 return split1.accountId() == split2.accountId(); 0096 } 0097 0098 bool TransactionMatchFinder::splitsAmountsMatch(const MyMoneySplit& split1, const MyMoneySplit& split2, int amountVariation) const 0099 { 0100 MyMoneyMoney upper(split1.shares()); 0101 MyMoneyMoney lower(upper); 0102 if ((amountVariation > 0) && (amountVariation < 100)) { 0103 lower = lower - (lower.abs() * MyMoneyMoney(amountVariation, 100)); 0104 upper = upper + (upper.abs() * MyMoneyMoney(amountVariation, 100)); 0105 } 0106 0107 return (split2.shares() >= lower) && (split2.shares() <= upper); 0108 } 0109 0110 bool TransactionMatchFinder::splitsBankIdsDuplicated(const MyMoneySplit& split1, const MyMoneySplit& split2) const 0111 { 0112 return (!split1.bankID().isEmpty()) && (split1.bankID() == split2.bankID()); 0113 } 0114 0115 bool TransactionMatchFinder::splitsBankIdsMatch(const MyMoneySplit& importedSplit, const MyMoneySplit& existingSplit) const 0116 { 0117 return (existingSplit.bankID().isEmpty() || existingSplit.bankID() == importedSplit.bankID()); 0118 } 0119 0120 bool TransactionMatchFinder::splitsPayeesMatchOrEmpty(const MyMoneySplit& split1, const MyMoneySplit& split2) const 0121 { 0122 bool payeesMatch = (split1.payeeId() == split2.payeeId()); 0123 bool atLeastOnePayeeIsNotSet = (split1.payeeId().isEmpty() || split2.payeeId().isEmpty()); 0124 return payeesMatch || atLeastOnePayeeIsNotSet; 0125 } 0126 0127 void TransactionMatchFinder::findMatchingSplit(const MyMoneyTransaction& transaction, int amountVariation) 0128 { 0129 foreach (const MyMoneySplit & split, transaction.splits()) { 0130 if (splitsAreDuplicates(m_importedSplit, split, amountVariation)) { 0131 matchedTransaction.reset(new MyMoneyTransaction(transaction)); 0132 matchedSplit.reset(new MyMoneySplit(split)); 0133 matchResult = MatchDuplicate; 0134 break; 0135 } 0136 0137 if (splitsMatch(m_importedSplit, split, amountVariation)) { 0138 matchedTransaction.reset(new MyMoneyTransaction(transaction)); 0139 matchedSplit.reset(new MyMoneySplit(split)); 0140 0141 bool datesMatchPrecisely = importedTransaction.postDate() == transaction.postDate(); 0142 if (datesMatchPrecisely) { 0143 matchResult = MatchPrecise; 0144 } else { 0145 matchResult = MatchImprecise; 0146 } 0147 break; 0148 } 0149 } 0150 }