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 }