Changeset View
Changeset View
Standalone View
Standalone View
kmymoney/dialogs/transactionmatcher.cpp
Context not available. | |||||
61 | void TransactionMatcher::match(MyMoneyTransaction tm, MyMoneySplit sm, MyMoneyTransaction ti, MyMoneySplit si, bool allowImportedTransactions) | 61 | void TransactionMatcher::match(MyMoneyTransaction tm, MyMoneySplit sm, MyMoneyTransaction ti, MyMoneySplit si, bool allowImportedTransactions) | ||
---|---|---|---|---|---|
62 | { | 62 | { | ||
63 | Q_D(TransactionMatcher); | 63 | Q_D(TransactionMatcher); | ||
64 | auto sec = MyMoneyFile::instance()->security(d->m_account.currencyId()); | 64 | const auto &file = MyMoneyFile::instance(); | ||
65 | auto sec = file->security(d->m_account.currencyId()); | ||||
65 | 66 | | |||
66 | // Now match the transactions. | 67 | // Now match the transactions. | ||
67 | // | 68 | // | ||
68 | // 'Matching' the transactions entails DELETING the end transaction, | 69 | // 'Matching' the transactions entails CREATING new transaction which | ||
69 | // and MODIFYING the start transaction as needed. | 70 | // has parameters of matched transactions. The matched transactions | ||
71 | // change their origin attribute and shouldn't be visible | ||||
72 | // by default anywhere. Only the new transaction will be visible. | ||||
70 | // | 73 | // | ||
71 | // There are a variety of ways that a transaction can conflict. | 74 | // There are a variety of ways that a transaction can conflict. | ||
72 | // Post date, splits, amount are the ones that seem to matter. | 75 | // Post date, splits, amount are the ones that seem to matter. | ||
Context not available. | |||||
105 | throw MYMONEYEXCEPTION(QString::fromLatin1("Splits for %1 have conflicting values (%2,%3)").arg(d->m_account.name(), MyMoneyUtils::formatMoney(sm.shares(), d->m_account, sec), MyMoneyUtils::formatMoney(si.shares(), d->m_account, sec))); | 108 | throw MYMONEYEXCEPTION(QString::fromLatin1("Splits for %1 have conflicting values (%2,%3)").arg(d->m_account.name(), MyMoneyUtils::formatMoney(sm.shares(), d->m_account, sec), MyMoneyUtils::formatMoney(si.shares(), d->m_account, sec))); | ||
106 | } | 109 | } | ||
107 | 110 | | |||
111 | if (ti.isMatched()) | ||||
112 | throw MYMONEYEXCEPTION_CSTRING("Second transaction is a product of match."); | ||||
113 | | ||||
114 | // if next match is comming, then we won't hide this transaction but update it | ||||
115 | const auto isTransactionAlreadyMatched = tm.isMatched(); | ||||
116 | if (!isTransactionAlreadyMatched) { | ||||
117 | // hide start transaction (input for matching) | ||||
118 | tm.setOrigin(static_cast<eMyMoney::Transaction::Origin>(tm.origin() | eMyMoney::Transaction::Origin::MatchingInput)); | ||||
119 | file->modifyTransaction(tm); | ||||
120 | // from now on tm is matched transaction (output of matching) | ||||
121 | tm.setOrigin(eMyMoney::Transaction::Origin::MatchingOutput); | ||||
122 | } | ||||
123 | | ||||
124 | // next match has matched matched transaction and it's id is meaningless in that case | ||||
125 | // so search for an ID of already matched transaction | ||||
126 | QString startTransactionID; | ||||
127 | if (isTransactionAlreadyMatched) { | ||||
128 | for (const auto &split : tm.splits()) { | ||||
129 | if (split.isMatched()) { | ||||
130 | startTransactionID = split.MyMoneyKeyValueContainer::value("kmm-match-transaction").split(';').first(); | ||||
131 | break; | ||||
132 | } | ||||
133 | } | ||||
134 | } else { | ||||
135 | startTransactionID = tm.id(); | ||||
136 | } | ||||
137 | | ||||
138 | // add details about ma | ||||
139 | sm.setValue("kmm-match-transaction", QString::fromLatin1("%1;%2").arg(startTransactionID, ti.id())); | ||||
140 | sm.setValue("kmm-match-split", QString::fromLatin1("%1;%2").arg(sm.id(), si.id())); | ||||
141 | | ||||
142 | // hide end transaction (input for matching) | ||||
143 | ti.setOrigin(static_cast<eMyMoney::Transaction::Origin>(ti.origin() | eMyMoney::Transaction::Origin::MatchingInput)); | ||||
144 | file->modifyTransaction(ti); | ||||
145 | | ||||
108 | // ipwizard: I took over the code to keep the bank id found in the endMatchTransaction | 146 | // ipwizard: I took over the code to keep the bank id found in the endMatchTransaction | ||
109 | // This might not work for QIF imports as they don't setup this information. It sure | 147 | // This might not work for QIF imports as they don't setup this information. It sure | ||
110 | // makes sense for OFX and HBCI. | 148 | // makes sense for OFX and HBCI. | ||
Context not available. | |||||
130 | 168 | | |||
131 | // if we don't have a payee assigned to the manually entered transaction | 169 | // if we don't have a payee assigned to the manually entered transaction | ||
132 | // we use the one we found in the imported transaction | 170 | // we use the one we found in the imported transaction | ||
133 | if (sm.payeeId().isEmpty() && !si.payeeId().isEmpty()) { | 171 | if (sm.payeeId().isEmpty() && !si.payeeId().isEmpty()) | ||
134 | sm.setValue("kmm-orig-payee", sm.payeeId()); | | |||
135 | sm.setPayeeId(si.payeeId()); | 172 | sm.setPayeeId(si.payeeId()); | ||
136 | } | | |||
137 | 173 | | |||
138 | // We use the imported postdate and keep the previous one for unmatch | 174 | // We use the imported postdate and keep the previous one for unmatch | ||
139 | if (tm.postDate() != ti.postDate()) { | 175 | if (tm.postDate() != ti.postDate()) | ||
140 | sm.setValue("kmm-orig-postdate", tm.postDate().toString(Qt::ISODate)); | | |||
141 | tm.setPostDate(ti.postDate()); | 176 | tm.setPostDate(ti.postDate()); | ||
142 | } | | |||
143 | 177 | | |||
144 | // combine the two memos into one | 178 | // combine the two memos into one | ||
145 | QString memo = sm.memo(); | 179 | QString memo = sm.memo(); | ||
146 | if (!si.memo().isEmpty() && si.memo() != memo) { | 180 | if (!si.memo().isEmpty() && si.memo() != memo) { | ||
147 | sm.setValue("kmm-orig-memo", memo); | | |||
148 | if (!memo.isEmpty()) | 181 | if (!memo.isEmpty()) | ||
149 | memo += '\n'; | 182 | memo += '\n'; | ||
150 | memo += si.memo(); | 183 | memo += si.memo(); | ||
151 | } | 184 | } | ||
152 | sm.setMemo(memo); | 185 | sm.setMemo(memo); | ||
153 | 186 | | |||
154 | // remember the split we matched | 187 | sm.addMatch(); | ||
155 | sm.setValue("kmm-match-split", si.id()); | | |||
156 | | ||||
157 | sm.addMatch(ti); | | |||
158 | tm.modifySplit(sm); | 188 | tm.modifySplit(sm); | ||
159 | 189 | | |||
160 | ti.modifySplit(si);/// | 190 | ti.modifySplit(si); | ||
161 | MyMoneyFile::instance()->modifyTransaction(tm); | 191 | | ||
162 | // Delete the end transaction if it was stored in the engine | 192 | // if matched transaction already exists then update it | ||
163 | if (!ti.id().isEmpty()) | 193 | // if not then create it | ||
164 | MyMoneyFile::instance()->removeTransaction(ti); | 194 | if (isTransactionAlreadyMatched) { | ||
195 | file->modifyTransaction(tm); | ||||
196 | } else { | ||||
197 | tm.clearId(); | ||||
198 | file->addTransaction(tm); | ||||
199 | } | ||||
165 | } | 200 | } | ||
166 | 201 | | |||
167 | void TransactionMatcher::unmatch(const MyMoneyTransaction& _t, const MyMoneySplit& _s) | 202 | void TransactionMatcher::unmatch(const MyMoneyTransaction& _t, const MyMoneySplit& _s) | ||
168 | { | 203 | { | ||
169 | if (_s.isMatched()) { | 204 | if (!_s.isMatched()) | ||
170 | MyMoneyTransaction tm(_t); | 205 | return; | ||
171 | MyMoneySplit sm(_s); | | |||
172 | MyMoneyTransaction ti(sm.matchedTransaction()); | | |||
173 | MyMoneySplit si; | | |||
174 | // if we don't have a split, then we don't have a memo | | |||
175 | try { | | |||
176 | si = ti.splitById(sm.value("kmm-match-split")); | | |||
177 | } catch (const MyMoneyException &) { | | |||
178 | } | | |||
179 | sm.removeMatch(); | | |||
180 | | ||||
181 | // restore the postdate if modified | | |||
182 | if (!sm.value("kmm-orig-postdate").isEmpty()) { | | |||
183 | tm.setPostDate(QDate::fromString(sm.value("kmm-orig-postdate"), Qt::ISODate)); | | |||
184 | } | | |||
185 | 206 | | |||
186 | // restore payee if modified | 207 | const auto &file = MyMoneyFile::instance(); | ||
187 | if (!sm.value("kmm-orig-payee").isEmpty()) { | 208 | MyMoneyTransaction tm(_t); | ||
188 | sm.setPayeeId(sm.value("kmm-orig-payee")); | 209 | MyMoneySplit sm(_s); | ||
189 | } | 210 | for (const auto &transactionID : sm.matchedTransactionIDs()) { | ||
211 | auto inputTransaction = file->transaction(transactionID); | ||||
212 | // unhide start and end transaction | ||||
213 | inputTransaction.setOrigin(static_cast<eMyMoney::Transaction::Origin>(inputTransaction.origin() ^ eMyMoney::Transaction::Origin::MatchingInput)); | ||||
214 | file->modifyTransaction(inputTransaction); | ||||
215 | } | ||||
190 | 216 | | |||
191 | // restore memo if modified | 217 | // effectively remove matching information from a split | ||
192 | if (!sm.value("kmm-orig-memo").isEmpty()) { | 218 | sm.MyMoneyKeyValueContainer::deletePair("kmm-match-transaction"); | ||
193 | sm.setMemo(sm.value("kmm-orig-memo")); | 219 | sm.MyMoneyKeyValueContainer::deletePair("kmm-match-split"); | ||
194 | } | 220 | sm.removeMatch(); | ||
195 | 221 | | |||
196 | sm.deletePair("kmm-orig-postdate"); | 222 | tm.modifySplit(sm); | ||
197 | sm.deletePair("kmm-orig-payee"); | 223 | file->modifyTransaction(tm); | ||
198 | sm.deletePair("kmm-orig-memo"); | | |||
199 | sm.deletePair("kmm-match-split"); | | |||
200 | tm.modifySplit(sm); | | |||
201 | 224 | | |||
202 | MyMoneyFile::instance()->modifyTransaction(tm); | 225 | // if there is no another matched split in a transaction | ||
203 | MyMoneyFile::instance()->addTransaction(ti); | 226 | // then this transaction can be removed | ||
227 | auto isAnyMatchedSplitLeft = false; | ||||
228 | for (const auto &split : tm.splits()) { | ||||
229 | if (split.isMatched()) { | ||||
230 | isAnyMatchedSplitLeft = true; | ||||
231 | break; | ||||
232 | } | ||||
204 | } | 233 | } | ||
234 | | ||||
235 | if (!isAnyMatchedSplitLeft) | ||||
236 | file->removeTransaction(tm); | ||||
205 | } | 237 | } | ||
206 | 238 | | |||
207 | void TransactionMatcher::accept(const MyMoneyTransaction& _t, const MyMoneySplit& _s) | 239 | void TransactionMatcher::accept(const MyMoneyTransaction& _t, const MyMoneySplit& _s) | ||
208 | { | 240 | { | ||
209 | if (_s.isMatched()) { | 241 | if (!_s.isMatched()) | ||
210 | MyMoneyTransaction tm(_t); | 242 | return; | ||
211 | MyMoneySplit sm(_s); | 243 | MyMoneyTransaction tm(_t); | ||
212 | sm.removeMatch(); | 244 | MyMoneySplit sm(_s); | ||
213 | sm.deletePair("kmm-orig-postdate"); | 245 | | ||
214 | sm.deletePair("kmm-orig-payee"); | 246 | const auto &file = MyMoneyFile::instance(); | ||
215 | sm.deletePair("kmm-orig-memo"); | 247 | for (const auto &transactionID : sm.matchedTransactionIDs()) { | ||
216 | sm.deletePair("kmm-match-split"); | 248 | auto inputTransaction = file->transaction(transactionID); | ||
217 | tm.modifySplit(sm); | 249 | file->removeTransaction(inputTransaction); | ||
218 | | ||||
219 | MyMoneyFile::instance()->modifyTransaction(tm); | | |||
220 | } | 250 | } | ||
251 | | ||||
252 | sm.MyMoneyKeyValueContainer::deletePair("kmm-match-transaction"); | ||||
253 | sm.MyMoneyKeyValueContainer::deletePair("kmm-match-split"); | ||||
254 | sm.removeMatch(); | ||||
255 | tm.modifySplit(sm); | ||||
256 | | ||||
257 | file->modifyTransaction(tm); | ||||
221 | } | 258 | } | ||
Context not available. |