File indexing completed on 2024-06-09 05:03:27
0001 /* 0002 SPDX-FileCopyrightText: 2000, 2003 Michael Edwardes <mte@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2003, 2023 Thomas Baumgart <tbaumgart@kde.org> 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kendingbalancedlg.h" 0008 0009 // ---------------------------------------------------------------------------- 0010 // QT Includes 0011 0012 #include <QAbstractButton> 0013 #include <QBitArray> 0014 #include <QDate> 0015 #include <QList> 0016 0017 // ---------------------------------------------------------------------------- 0018 // KDE Includes 0019 0020 #include <KHelpClient> 0021 #include <KMessageBox> 0022 #include <KStandardGuiItem> 0023 0024 // ---------------------------------------------------------------------------- 0025 // Project Includes 0026 0027 #include "ui_checkingstatementinfowizardpage.h" 0028 #include "ui_interestchargecheckingswizardpage.h" 0029 #include "ui_kendingbalancedlg.h" 0030 0031 #include "kcurrencycalculator.h" 0032 #include "kmymoneycategory.h" 0033 #include "kmymoneysettings.h" 0034 #include "kmymoneyutils.h" 0035 #include "knewaccountdlg.h" 0036 #include "mymoneyaccount.h" 0037 #include "mymoneyenums.h" 0038 #include "mymoneyexception.h" 0039 #include "mymoneyfile.h" 0040 #include "mymoneyinstitution.h" 0041 #include "mymoneymoney.h" 0042 #include "mymoneypayee.h" 0043 #include "mymoneysecurity.h" 0044 #include "mymoneysplit.h" 0045 #include "mymoneytracer.h" 0046 #include "mymoneytransaction.h" 0047 #include "mymoneytransactionfilter.h" 0048 #include "mymoneyutils.h" 0049 0050 class KEndingBalanceDlgPrivate 0051 { 0052 Q_DISABLE_COPY(KEndingBalanceDlgPrivate) 0053 0054 public: 0055 KEndingBalanceDlgPrivate(KEndingBalanceDlg* qq, int numPages) 0056 : q(qq) 0057 , ui(new Ui::KEndingBalanceDlg) 0058 , m_pages(numPages, true) 0059 { 0060 } 0061 0062 ~KEndingBalanceDlgPrivate() 0063 { 0064 delete ui; 0065 } 0066 0067 KEndingBalanceDlg* q; 0068 Ui::KEndingBalanceDlg* ui; 0069 MyMoneyTransaction m_tInterest; 0070 MyMoneyTransaction m_tCharges; 0071 MyMoneyAccount m_account; 0072 QMap<QWidget*, QString> m_helpAnchor; 0073 QBitArray m_pages; 0074 QDate m_startDate; 0075 }; 0076 0077 KEndingBalanceDlg::KEndingBalanceDlg(const MyMoneyAccount& account, QWidget* parent) 0078 : QWizard(parent) 0079 , d_ptr(new KEndingBalanceDlgPrivate(this, Page_InterestChargeCheckings + 1)) 0080 { 0081 Q_D(KEndingBalanceDlg); 0082 setModal(true); 0083 QString value; 0084 MyMoneyMoney endBalance, startBalance; 0085 0086 d->ui->setupUi(this); 0087 d->m_account = account; 0088 0089 MyMoneySecurity currency = MyMoneyFile::instance()->security(account.currencyId()); 0090 //FIXME: port 0091 d->ui->m_statementInfoPageCheckings->ui->m_enterInformationLabel->setText(QString("<qt>") + i18n("Please enter the following fields with the information as you find them on your statement. Make sure to enter all values in <b>%1</b>.", currency.name()) + QString("</qt>")); 0092 0093 // If the previous reconciliation was postponed, 0094 // we show a different first page 0095 value = account.value("lastReconciledBalance"); 0096 if (value.isEmpty()) { 0097 // if the last statement has been entered long enough ago (more than one month), 0098 // then take the last statement date and add one month and use that as statement 0099 // date. 0100 QDate lastStatementDate = account.lastReconciliationDate(); 0101 if (lastStatementDate.isValid() && (lastStatementDate.addMonths(1) < QDate::currentDate())) { 0102 setField("statementDate", lastStatementDate.addMonths(1)); 0103 } 0104 0105 slotUpdateBalances(); 0106 0107 d->m_pages.clearBit(Page_PreviousPostpone); 0108 } else { 0109 d->m_pages.clearBit(Page_CheckingStart); 0110 d->m_pages.clearBit(Page_InterestChargeCheckings); 0111 //removePage(d->ui->m_interestChargeCheckings); 0112 // make sure, we show the correct start page 0113 setStartId(Page_PreviousPostpone); 0114 0115 MyMoneyMoney factor(1, 1); 0116 if (d->m_account.accountGroup() == eMyMoney::Account::Type::Liability) 0117 factor = -factor; 0118 0119 startBalance = MyMoneyMoney(value) * factor; 0120 value = account.value("statementBalance"); 0121 endBalance = MyMoneyMoney(value) * factor; 0122 0123 //FIXME: port 0124 d->ui->m_statementInfoPageCheckings->ui->m_previousBalance->setValue(startBalance); 0125 d->ui->m_statementInfoPageCheckings->ui->m_endingBalance->setValue(endBalance); 0126 } 0127 0128 // We don't need to add the default into the list (see ::help() why) 0129 // m_helpAnchor[m_startPageCheckings] = QString(QString()); 0130 d->m_helpAnchor[d->ui->m_interestChargeCheckings] = QString("details.reconcile.wizard.interest"); 0131 d->m_helpAnchor[d->ui->m_statementInfoPageCheckings] = QString("details.reconcile.wizard.statement"); 0132 0133 value = account.value("statementDate"); 0134 if (!value.isEmpty()) { 0135 setField("statementDate", QDate::fromString(value, Qt::ISODate)); 0136 } 0137 //FIXME: port 0138 d->ui->m_statementInfoPageCheckings->ui->m_lastStatementDate->setText(QString()); 0139 if (account.lastReconciliationDate().isValid()) { 0140 d->ui->m_statementInfoPageCheckings->ui->m_lastStatementDate->setText( 0141 i18n("Last reconciled statement: %1", MyMoneyUtils::formatDate(account.lastReconciliationDate()))); 0142 } 0143 0144 // connect the signals with the slots 0145 connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KEndingBalanceDlg::slotReloadEditWidgets); 0146 connect(d->ui->m_statementInfoPageCheckings->ui->m_statementDate, &KMyMoneyDateEdit::dateChanged, this, &KEndingBalanceDlg::slotUpdateBalances); 0147 connect(d->ui->m_interestChargeCheckings->ui->m_interestCategoryEdit, &KMyMoneyCombo::createItem, this, &KEndingBalanceDlg::slotCreateInterestCategory); 0148 connect(d->ui->m_interestChargeCheckings->ui->m_chargesCategoryEdit, &KMyMoneyCombo::createItem, this, &KEndingBalanceDlg::slotCreateChargesCategory); 0149 connect(d->ui->m_interestChargeCheckings->ui->m_payeeEdit, &KMyMoneyMVCCombo::createItem, this, &KEndingBalanceDlg::slotNewPayee); 0150 0151 KMyMoneyMVCCombo::setSubstringSearchForChildren(d->ui->m_interestChargeCheckings, !KMyMoneySettings::stringMatchFromStart()); 0152 0153 slotReloadEditWidgets(); 0154 0155 // preset payee if possible 0156 try { 0157 // if we find a payee with the same name as the institution, 0158 // than this is what we use as payee. 0159 if (!d->m_account.institutionId().isEmpty()) { 0160 MyMoneyInstitution inst = MyMoneyFile::instance()->institution(d->m_account.institutionId()); 0161 MyMoneyPayee payee = MyMoneyFile::instance()->payeeByName(inst.name()); 0162 setField("payeeEdit", payee.id()); 0163 } 0164 } catch (const MyMoneyException &) { 0165 } 0166 0167 KMyMoneyUtils::updateWizardButtons(this); 0168 0169 // setup different text and icon on finish button 0170 setButtonText(QWizard::FinishButton, KStandardGuiItem::cont().text()); 0171 button(QWizard::FinishButton)->setIcon(KStandardGuiItem::cont().icon()); 0172 } 0173 0174 KEndingBalanceDlg::~KEndingBalanceDlg() 0175 { 0176 Q_D(KEndingBalanceDlg); 0177 delete d; 0178 } 0179 0180 void KEndingBalanceDlg::slotUpdateBalances() 0181 { 0182 Q_D(KEndingBalanceDlg); 0183 MYMONEYTRACER(tracer); 0184 0185 // determine the beginning balance and ending balance based on the following 0186 // formulas: 0187 // 0188 // end balance = current balance - sum(all non cleared transactions) 0189 // - sum(all cleared transactions posted 0190 // after statement date) 0191 // start balance = end balance - sum(all cleared transactions 0192 // up to statement date) 0193 MyMoneyTransactionFilter filter(d->m_account.id()); 0194 filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); 0195 filter.setReportAllSplits(true); 0196 0197 QList<QPair<MyMoneyTransaction, MyMoneySplit> > transactionList; 0198 QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it; 0199 0200 // retrieve the list from the engine 0201 MyMoneyFile::instance()->transactionList(transactionList, filter); 0202 0203 //first retrieve the oldest not reconciled transaction 0204 QDate oldestTransactionDate; 0205 it = transactionList.constBegin(); 0206 if (it != transactionList.constEnd()) { 0207 oldestTransactionDate = (*it).first.postDate(); 0208 d->ui->m_statementInfoPageCheckings->ui->m_oldestTransactionDate->setText( 0209 i18n("Oldest unmarked transaction: %1", MyMoneyUtils::formatDate(oldestTransactionDate))); 0210 } 0211 0212 filter.clear(); 0213 filter.addAccount(d->m_account.id()); 0214 0215 // retrieve the list from the engine to calculate the starting and ending balance 0216 MyMoneyFile::instance()->transactionList(transactionList, filter); 0217 0218 // set the balance to the value of the account at the end of the ledger 0219 // This includes all transactions even those past the statement date. 0220 MyMoneyMoney balance = MyMoneyFile::instance()->balance(d->m_account.id()); 0221 MyMoneyMoney factor(1, 1); 0222 if (d->m_account.accountGroup() == eMyMoney::Account::Type::Liability) 0223 factor = -factor; 0224 0225 MyMoneyMoney endBalance, startBalance; 0226 balance = balance * factor; 0227 endBalance = startBalance = balance; 0228 0229 tracer.printf("total balance = %s", qPrintable(endBalance.formatMoney(QString(), 2))); 0230 0231 d->m_startDate = d->m_account.lastReconciliationDate(); 0232 0233 // now adjust the balances by reading all transactions referencing the account 0234 // from beginning to the end of the ledger 0235 for (it = transactionList.constBegin(); it != transactionList.constEnd(); ++it) { 0236 const MyMoneySplit& split = (*it).second; 0237 balance -= split.shares() * factor; 0238 if ((*it).first.postDate() > field("statementDate").toDate()) { 0239 // in case the transaction's post date is younger than the statement date 0240 // we need to subtract the value from the balances. 0241 0242 tracer.printf("Reducing balances by %s because postdate of %s/%s(%s) is past statement date", qPrintable((split.shares() * factor).formatMoney(QString(), 2)), qPrintable((*it).first.id()), qPrintable(split.id()), qPrintable((*it).first.postDate().toString(Qt::ISODate))); 0243 endBalance -= split.shares() * factor; 0244 startBalance -= split.shares() * factor; 0245 0246 } else { 0247 // in case the transaction's post date is older or equal to 0248 // the statement date we need to check what to do 0249 switch (split.reconcileFlag()) { 0250 case eMyMoney::Split::State::NotReconciled: 0251 // in case it is not marked at all we need to check if 0252 // it is necessary to adjust the start date of the 0253 // display. 0254 if ((*it).first.postDate() < d->m_startDate) { 0255 d->m_startDate = (*it).first.postDate(); 0256 } 0257 0258 // subtract it from the balances 0259 tracer.printf("Reducing balances by %s because %s/%s(%s) is not reconciled", qPrintable((split.shares() * factor).formatMoney(QString(), 2)), qPrintable((*it).first.id()), qPrintable(split.id()), qPrintable((*it).first.postDate().toString(Qt::ISODate))); 0260 endBalance -= split.shares() * factor; 0261 startBalance -= split.shares() * factor; 0262 break; 0263 0264 case eMyMoney::Split::State::Cleared: 0265 // in case it is marked as cleared we need to check if 0266 // it is necessary to adjust the start date of the 0267 // display. It could be, that this transaction is 0268 // older than the statement date. 0269 if ((*it).first.postDate() < d->m_startDate) { 0270 d->m_startDate = (*it).first.postDate(); 0271 } 0272 Q_FALLTHROUGH(); 0273 0274 case eMyMoney::Split::State::Reconciled: 0275 case eMyMoney::Split::State::Frozen: 0276 // in case it is marked as cleared, reconciled or frozen 0277 // we need to check if the transaction is found after 0278 // the current start date. If so, we need to adjust the 0279 // startBalance but don't touch the endBalance 0280 if ((*it).first.postDate() >= d->m_startDate) { 0281 tracer.printf("Reducing start balance by %s because %s/%s(%s) is cleared/reconciled", 0282 qPrintable((split.shares() * factor).formatMoney(QString(), 2)), 0283 qPrintable((*it).first.id()), 0284 qPrintable(split.id()), 0285 qPrintable((*it).first.postDate().toString(Qt::ISODate))); 0286 startBalance -= split.shares() * factor; 0287 } 0288 break; 0289 0290 default: 0291 break; 0292 } 0293 } 0294 } 0295 //FIXME: port 0296 d->ui->m_statementInfoPageCheckings->ui->m_previousBalance->setValue(startBalance); 0297 d->ui->m_statementInfoPageCheckings->ui->m_endingBalance->setValue(endBalance); 0298 tracer.printf("total balance = %s", qPrintable(endBalance.formatMoney(QString(), 2))); 0299 tracer.printf("start balance = %s", qPrintable(startBalance.formatMoney(QString(), 2))); 0300 0301 setField("interestDateEdit", field("statementDate").toDate()); 0302 setField("chargesDateEdit", field("statementDate").toDate()); 0303 } 0304 0305 void KEndingBalanceDlg::accept() 0306 { 0307 Q_D(KEndingBalanceDlg); 0308 if ((!field("interestEditValid").toBool() || createTransaction(d->m_tInterest, -1, field("interestEdit").value<MyMoneyMoney>(), field("interestCategoryEdit").toString(), field("interestDateEdit").toDate())) 0309 && (!field("chargesEditValid").toBool() || createTransaction(d->m_tCharges, 1, field("chargesEdit").value<MyMoneyMoney>(), field("chargesCategoryEdit").toString(), field("chargesDateEdit").toDate()))) 0310 QWizard::accept(); 0311 } 0312 0313 void KEndingBalanceDlg::slotCreateInterestCategory(const QString& txt, QString& id) 0314 { 0315 createCategory(txt, id, MyMoneyFile::instance()->income()); 0316 } 0317 0318 void KEndingBalanceDlg::slotCreateChargesCategory(const QString& txt, QString& id) 0319 { 0320 createCategory(txt, id, MyMoneyFile::instance()->expense()); 0321 } 0322 0323 void KEndingBalanceDlg::createCategory(const QString& txt, QString& id, const MyMoneyAccount& parent) 0324 { 0325 MyMoneyAccount acc; 0326 acc.setName(txt); 0327 0328 KNewAccountDlg::createCategory(acc, parent); 0329 0330 id = acc.id(); 0331 } 0332 0333 void KEndingBalanceDlg::slotNewPayee(const QString& newnameBase, QString& id) 0334 { 0335 bool ok; 0336 std::tie(ok, id) = KMyMoneyUtils::newPayee(newnameBase); 0337 } 0338 0339 MyMoneyMoney KEndingBalanceDlg::endingBalance() const 0340 { 0341 Q_D(const KEndingBalanceDlg); 0342 return adjustedReturnValue(d->ui->m_statementInfoPageCheckings->ui->m_endingBalance->value()); 0343 } 0344 0345 MyMoneyMoney KEndingBalanceDlg::previousBalance() const 0346 { 0347 Q_D(const KEndingBalanceDlg); 0348 return adjustedReturnValue(d->ui->m_statementInfoPageCheckings->ui->m_previousBalance->value()); 0349 } 0350 0351 QDate KEndingBalanceDlg::statementDate() const 0352 { 0353 return field("statementDate").toDate(); 0354 } 0355 0356 MyMoneyMoney KEndingBalanceDlg::adjustedReturnValue(const MyMoneyMoney& v) const 0357 { 0358 Q_D(const KEndingBalanceDlg); 0359 return d->m_account.accountGroup() == eMyMoney::Account::Type::Liability ? -v : v; 0360 } 0361 0362 void KEndingBalanceDlg::slotReloadEditWidgets() 0363 { 0364 Q_D(KEndingBalanceDlg); 0365 QString payeeId, interestId, chargesId; 0366 0367 // keep current selected items 0368 payeeId = field("payeeEdit").toString(); 0369 interestId = field("interestCategoryEdit").toString(); 0370 chargesId = field("chargesCategoryEdit").toString(); 0371 0372 // load the payee and category widgets with data from the engine 0373 //FIXME: port 0374 d->ui->m_interestChargeCheckings->ui->m_payeeEdit->loadPayees(MyMoneyFile::instance()->payeeList()); 0375 0376 // a user request to show all categories in both selectors due to a valid use case. 0377 AccountSet aSet; 0378 aSet.addAccountGroup(eMyMoney::Account::Type::Expense); 0379 aSet.addAccountGroup(eMyMoney::Account::Type::Income); 0380 //FIXME: port 0381 aSet.load(d->ui->m_interestChargeCheckings->ui->m_interestCategoryEdit->selector()); 0382 aSet.load(d->ui->m_interestChargeCheckings->ui->m_chargesCategoryEdit->selector()); 0383 0384 // reselect currently selected items 0385 if (!payeeId.isEmpty()) 0386 setField("payeeEdit", payeeId); 0387 if (!interestId.isEmpty()) 0388 setField("interestCategoryEdit", interestId); 0389 if (!chargesId.isEmpty()) 0390 setField("chargesCategoryEdit", chargesId); 0391 } 0392 0393 MyMoneyTransaction KEndingBalanceDlg::interestTransaction() 0394 { 0395 Q_D(KEndingBalanceDlg); 0396 return d->m_tInterest; 0397 } 0398 0399 MyMoneyTransaction KEndingBalanceDlg::chargeTransaction() 0400 { 0401 Q_D(KEndingBalanceDlg); 0402 return d->m_tCharges; 0403 } 0404 0405 bool KEndingBalanceDlg::createTransaction(MyMoneyTransaction &t, const int sign, const MyMoneyMoney& amount, const QString& category, const QDate& date) 0406 { 0407 Q_D(KEndingBalanceDlg); 0408 t = MyMoneyTransaction(); 0409 0410 if (category.isEmpty() || !date.isValid()) 0411 return true; 0412 0413 MyMoneySplit s1, s2; 0414 MyMoneyMoney val = amount * MyMoneyMoney(sign, 1); 0415 try { 0416 t.setPostDate(date); 0417 t.setCommodity(d->m_account.currencyId()); 0418 0419 s1.setPayeeId(field("payeeEdit").toString()); 0420 s1.setReconcileFlag(eMyMoney::Split::State::Cleared); 0421 s1.setAccountId(d->m_account.id()); 0422 s1.setValue(-val); 0423 s1.setShares(-val); 0424 0425 s2 = s1; 0426 s2.setAccountId(category); 0427 s2.setValue(val); 0428 0429 t.addSplit(s1); 0430 t.addSplit(s2); 0431 0432 QMap<QString, MyMoneyMoney> priceInfo; // just empty 0433 MyMoneyMoney shares; 0434 if (!KCurrencyCalculator::setupSplitPrice(shares, t, s2, priceInfo, this)) { 0435 t = MyMoneyTransaction(); 0436 return false; 0437 } 0438 0439 s2.setShares(shares); 0440 t.modifySplit(s2); 0441 0442 } catch (const MyMoneyException &e) { 0443 qDebug("%s", e.what()); 0444 t = MyMoneyTransaction(); 0445 return false; 0446 } 0447 0448 return true; 0449 } 0450 0451 void KEndingBalanceDlg::help() 0452 { 0453 Q_D(KEndingBalanceDlg); 0454 QString anchor = d->m_helpAnchor[currentPage()]; 0455 if (anchor.isEmpty()) 0456 anchor = QString("details.reconcile.whatis"); 0457 0458 KHelpClient::invokeHelp(anchor); 0459 } 0460 0461 int KEndingBalanceDlg::nextId() const 0462 { 0463 Q_D(const KEndingBalanceDlg); 0464 // Starting from the current page, look for the first enabled page 0465 // and return that value 0466 // If the end of the list is encountered first, then return -1. 0467 for (int i = currentId() + 1; i < d->m_pages.size() && i < pageIds().size(); ++i) { 0468 if (d->m_pages.testBit(i)) 0469 return pageIds()[i]; 0470 } 0471 return -1; 0472 } 0473 0474 QDate KEndingBalanceDlg::startDate() const 0475 { 0476 Q_D(const KEndingBalanceDlg); 0477 return d->m_startDate; 0478 }