1 /***************************************************************************
2                           kendingbalancedlg.cpp
3                              -------------------
4     copyright            : (C) 2000,2003 by Michael Edwardes, Thomas Baumgart
5     email                : mte@users.sourceforge.net
6                            ipwizard@users.sourceforge.net
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "kendingbalancedlg.h"
19 
20 // ----------------------------------------------------------------------------
21 // QT Includes
22 
23 #include <QDate>
24 #include <QList>
25 #include <QBitArray>
26 #include <QAbstractButton>
27 
28 // ----------------------------------------------------------------------------
29 // KDE Includes
30 
31 #include <KStandardGuiItem>
32 #include <KHelpClient>
33 
34 // ----------------------------------------------------------------------------
35 // Project Includes
36 
37 #include "ui_kendingbalancedlg.h"
38 #include "ui_checkingstatementinfowizardpage.h"
39 #include "ui_interestchargecheckingswizardpage.h"
40 
41 #include "mymoneymoney.h"
42 #include "mymoneyexception.h"
43 #include "mymoneyutils.h"
44 #include "mymoneytracer.h"
45 #include "mymoneysplit.h"
46 #include "mymoneyfile.h"
47 #include "mymoneyinstitution.h"
48 #include "mymoneyaccount.h"
49 #include "mymoneypayee.h"
50 #include "mymoneysecurity.h"
51 #include "mymoneytransaction.h"
52 #include "mymoneytransactionfilter.h"
53 #include "kmymoneycategory.h"
54 #include "kmymoneyaccountselector.h"
55 #include "kmymoneyutils.h"
56 #include "kcurrencycalculator.h"
57 #include "kmymoneysettings.h"
58 #include "knewaccountdlg.h"
59 #include "mymoneyenums.h"
60 
61 class KEndingBalanceDlgPrivate
62 {
63   Q_DISABLE_COPY(KEndingBalanceDlgPrivate)
64 
65 public:
66 
KEndingBalanceDlgPrivate(int numPages)67   KEndingBalanceDlgPrivate(int numPages) :
68     ui(new Ui::KEndingBalanceDlg),
69     m_pages(numPages, true)
70   {
71   }
72 
~KEndingBalanceDlgPrivate()73   ~KEndingBalanceDlgPrivate()
74   {
75     delete ui;
76   }
77 
78   Ui::KEndingBalanceDlg    *ui;
79   MyMoneyTransaction        m_tInterest;
80   MyMoneyTransaction        m_tCharges;
81   MyMoneyAccount            m_account;
82   QMap<QWidget*, QString>   m_helpAnchor;
83   QBitArray m_pages;
84 };
85 
KEndingBalanceDlg(const MyMoneyAccount & account,QWidget * parent)86 KEndingBalanceDlg::KEndingBalanceDlg(const MyMoneyAccount& account, QWidget *parent) :
87     QWizard(parent),
88     d_ptr(new KEndingBalanceDlgPrivate(Page_InterestChargeCheckings + 1))
89 {
90   Q_D(KEndingBalanceDlg);
91   setModal(true);
92   QString value;
93   MyMoneyMoney endBalance, startBalance;
94 
95   d->ui->setupUi(this);
96   d->m_account = account;
97 
98   MyMoneySecurity currency = MyMoneyFile::instance()->security(account.currencyId());
99   //FIXME: port
100   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>"));
101 
102   // If the previous reconciliation was postponed,
103   // we show a different first page
104   value = account.value("lastReconciledBalance");
105   if (value.isEmpty()) {
106     // if the last statement has been entered long enough ago (more than one month),
107     // then take the last statement date and add one month and use that as statement
108     // date.
109     QDate lastStatementDate = account.lastReconciliationDate();
110     if (lastStatementDate.addMonths(1) < QDate::currentDate()) {
111       setField("statementDate", lastStatementDate.addMonths(1));
112     }
113 
114     slotUpdateBalances();
115 
116     d->m_pages.clearBit(Page_PreviousPostpone);
117   } else {
118     d->m_pages.clearBit(Page_CheckingStart);
119     d->m_pages.clearBit(Page_InterestChargeCheckings);
120     //removePage(d->ui->m_interestChargeCheckings);
121     // make sure, we show the correct start page
122     setStartId(Page_PreviousPostpone);
123 
124     MyMoneyMoney factor(1, 1);
125     if (d->m_account.accountGroup() == eMyMoney::Account::Type::Liability)
126       factor = -factor;
127 
128     startBalance = MyMoneyMoney(value) * factor;
129     value = account.value("statementBalance");
130     endBalance = MyMoneyMoney(value) * factor;
131 
132     //FIXME: port
133     d->ui->m_statementInfoPageCheckings->ui->m_previousBalance->setValue(startBalance);
134     d->ui->m_statementInfoPageCheckings->ui->m_endingBalance->setValue(endBalance);
135   }
136 
137   // We don't need to add the default into the list (see ::help() why)
138   // m_helpAnchor[m_startPageCheckings] = QString(QString());
139   d->m_helpAnchor[d->ui->m_interestChargeCheckings] = QString("details.reconcile.wizard.interest");
140   d->m_helpAnchor[d->ui->m_statementInfoPageCheckings] = QString("details.reconcile.wizard.statement");
141 
142   value = account.value("statementDate");
143   if (!value.isEmpty())
144     setField("statementDate", QDate::fromString(value, Qt::ISODate));
145 
146   //FIXME: port
147   d->ui->m_statementInfoPageCheckings->ui->m_lastStatementDate->setText(QString());
148   if (account.lastReconciliationDate().isValid()) {
149     d->ui->m_statementInfoPageCheckings->ui->m_lastStatementDate->setText(i18n("Last reconciled statement: %1", QLocale().toString(account.lastReconciliationDate())));
150   }
151 
152   // connect the signals with the slots
153   connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KEndingBalanceDlg::slotReloadEditWidgets);
154   connect(d->ui->m_statementInfoPageCheckings->ui->m_statementDate, &KMyMoneyDateInput::dateChanged, this, &KEndingBalanceDlg::slotUpdateBalances);
155   connect(d->ui->m_interestChargeCheckings->ui->m_interestCategoryEdit, &KMyMoneyCombo::createItem, this, &KEndingBalanceDlg::slotCreateInterestCategory);
156   connect(d->ui->m_interestChargeCheckings->ui->m_chargesCategoryEdit, &KMyMoneyCombo::createItem, this, &KEndingBalanceDlg::slotCreateChargesCategory);
157   connect(d->ui->m_interestChargeCheckings->ui->m_payeeEdit, &KMyMoneyMVCCombo::createItem, this, &KEndingBalanceDlg::slotNewPayee);
158 
159   KMyMoneyMVCCombo::setSubstringSearchForChildren(d->ui->m_interestChargeCheckings, !KMyMoneySettings::stringMatchFromStart());
160 
161   slotReloadEditWidgets();
162 
163   // preset payee if possible
164   try {
165     // if we find a payee with the same name as the institution,
166     // than this is what we use as payee.
167     if (!d->m_account.institutionId().isEmpty()) {
168       MyMoneyInstitution inst = MyMoneyFile::instance()->institution(d->m_account.institutionId());
169       MyMoneyPayee payee = MyMoneyFile::instance()->payeeByName(inst.name());
170       setField("payeeEdit", payee.id());
171     }
172   } catch (const MyMoneyException &) {
173   }
174 
175   KMyMoneyUtils::updateWizardButtons(this);
176 
177   // setup different text and icon on finish button
178   setButtonText(QWizard::FinishButton, KStandardGuiItem::cont().text());
179   button(QWizard::FinishButton)->setIcon(KStandardGuiItem::cont().icon());
180 }
181 
~KEndingBalanceDlg()182 KEndingBalanceDlg::~KEndingBalanceDlg()
183 {
184   Q_D(KEndingBalanceDlg);
185   delete d;
186 }
187 
slotUpdateBalances()188 void KEndingBalanceDlg::slotUpdateBalances()
189 {
190   Q_D(KEndingBalanceDlg);
191   MYMONEYTRACER(tracer);
192 
193   // determine the beginning balance and ending balance based on the following
194   // forumulas:
195   //
196   // end balance   = current balance - sum(all non cleared transactions)
197   //                                 - sum(all cleared transactions posted
198   //                                        after statement date)
199   // start balance = end balance - sum(all cleared transactions
200   //                                        up to statement date)
201   MyMoneyTransactionFilter filter(d->m_account.id());
202   filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled);
203   filter.setReportAllSplits(true);
204 
205   QList<QPair<MyMoneyTransaction, MyMoneySplit> > transactionList;
206   QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
207 
208   // retrieve the list from the engine
209   MyMoneyFile::instance()->transactionList(transactionList, filter);
210 
211   //first retrieve the oldest not reconciled transaction
212   QDate oldestTransactionDate;
213   it = transactionList.constBegin();
214   if (it != transactionList.constEnd()) {
215     oldestTransactionDate = (*it).first.postDate();
216     d->ui->m_statementInfoPageCheckings->ui->m_oldestTransactionDate->setText(i18n("Oldest unmarked transaction: %1", QLocale().toString(oldestTransactionDate)));
217   }
218 
219   filter.addState((int)eMyMoney::TransactionFilter::State::Cleared);
220 
221   // retrieve the list from the engine to calculate the starting and ending balance
222   MyMoneyFile::instance()->transactionList(transactionList, filter);
223 
224   MyMoneyMoney balance = MyMoneyFile::instance()->balance(d->m_account.id());
225   MyMoneyMoney factor(1, 1);
226   if (d->m_account.accountGroup() == eMyMoney::Account::Type::Liability)
227     factor = -factor;
228 
229   MyMoneyMoney endBalance, startBalance;
230   balance = balance * factor;
231   endBalance = startBalance = balance;
232 
233   tracer.printf("total balance = %s", qPrintable(endBalance.formatMoney(QString(), 2)));
234 
235   for (it = transactionList.constBegin(); it != transactionList.constEnd(); ++it) {
236     const MyMoneySplit& split = (*it).second;
237     balance -= split.shares() * factor;
238     if ((*it).first.postDate() > field("statementDate").toDate()) {
239       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)));
240       endBalance -= split.shares() * factor;
241       startBalance -= split.shares() * factor;
242     } else {
243       switch (split.reconcileFlag()) {
244         case eMyMoney::Split::State::NotReconciled:
245           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)));
246           endBalance -= split.shares() * factor;
247           startBalance -= split.shares() * factor;
248           break;
249         case eMyMoney::Split::State::Cleared:
250           tracer.printf("Reducing start balance by %s because %s/%s(%s) is cleared", qPrintable((split.shares() * factor).formatMoney(QString(), 2)), qPrintable((*it).first.id()), qPrintable(split.id()), qPrintable((*it).first.postDate().toString(Qt::ISODate)));
251           startBalance -= split.shares() * factor;
252           break;
253         default:
254           break;
255       }
256     }
257   }
258   //FIXME: port
259   d->ui->m_statementInfoPageCheckings->ui->m_previousBalance->setValue(startBalance);
260   d->ui->m_statementInfoPageCheckings->ui->m_endingBalance->setValue(endBalance);
261   tracer.printf("total balance = %s", qPrintable(endBalance.formatMoney(QString(), 2)));
262   tracer.printf("start balance = %s", qPrintable(startBalance.formatMoney(QString(), 2)));
263 
264   setField("interestDateEdit", field("statementDate").toDate());
265   setField("chargesDateEdit", field("statementDate").toDate());
266 }
267 
accept()268 void KEndingBalanceDlg::accept()
269 {
270   Q_D(KEndingBalanceDlg);
271   if ((!field("interestEditValid").toBool() || createTransaction(d->m_tInterest, -1, field("interestEdit").value<MyMoneyMoney>(), field("interestCategoryEdit").toString(), field("interestDateEdit").toDate()))
272       && (!field("chargesEditValid").toBool() || createTransaction(d->m_tCharges, 1, field("chargesEdit").value<MyMoneyMoney>(), field("chargesCategoryEdit").toString(), field("chargesDateEdit").toDate())))
273     QWizard::accept();
274 }
275 
slotCreateInterestCategory(const QString & txt,QString & id)276 void KEndingBalanceDlg::slotCreateInterestCategory(const QString& txt, QString& id)
277 {
278   createCategory(txt, id, MyMoneyFile::instance()->income());
279 }
280 
slotCreateChargesCategory(const QString & txt,QString & id)281 void KEndingBalanceDlg::slotCreateChargesCategory(const QString& txt, QString& id)
282 {
283   createCategory(txt, id, MyMoneyFile::instance()->expense());
284 }
285 
createCategory(const QString & txt,QString & id,const MyMoneyAccount & parent)286 void KEndingBalanceDlg::createCategory(const QString& txt, QString& id, const MyMoneyAccount& parent)
287 {
288   MyMoneyAccount acc;
289   acc.setName(txt);
290 
291   KNewAccountDlg::createCategory(acc, parent);
292 
293   id = acc.id();
294 }
295 
slotNewPayee(const QString & newnameBase,QString & id)296 void KEndingBalanceDlg::slotNewPayee(const QString& newnameBase, QString& id)
297 {
298   KMyMoneyUtils::newPayee(newnameBase, id);
299 }
300 
endingBalance() const301 MyMoneyMoney KEndingBalanceDlg::endingBalance() const
302 {
303   Q_D(const KEndingBalanceDlg);
304   return adjustedReturnValue(d->ui->m_statementInfoPageCheckings->ui->m_endingBalance->value());
305 }
306 
previousBalance() const307 MyMoneyMoney KEndingBalanceDlg::previousBalance() const
308 {
309   Q_D(const KEndingBalanceDlg);
310   return adjustedReturnValue(d->ui->m_statementInfoPageCheckings->ui->m_previousBalance->value());
311 }
312 
statementDate() const313 QDate KEndingBalanceDlg::statementDate() const
314 {
315   return field("statementDate").toDate();
316 }
317 
adjustedReturnValue(const MyMoneyMoney & v) const318 MyMoneyMoney KEndingBalanceDlg::adjustedReturnValue(const MyMoneyMoney& v) const
319 {
320   Q_D(const KEndingBalanceDlg);
321   return d->m_account.accountGroup() == eMyMoney::Account::Type::Liability ? -v : v;
322 }
323 
slotReloadEditWidgets()324 void KEndingBalanceDlg::slotReloadEditWidgets()
325 {
326   Q_D(KEndingBalanceDlg);
327   QString payeeId, interestId, chargesId;
328 
329   // keep current selected items
330   payeeId = field("payeeEdit").toString();
331   interestId = field("interestCategoryEdit").toString();
332   chargesId = field("chargesCategoryEdit").toString();
333 
334   // load the payee and category widgets with data from the engine
335   //FIXME: port
336   d->ui->m_interestChargeCheckings->ui->m_payeeEdit->loadPayees(MyMoneyFile::instance()->payeeList());
337 
338   // a user request to show all categories in both selectors due to a valid use case.
339   AccountSet aSet;
340   aSet.addAccountGroup(eMyMoney::Account::Type::Expense);
341   aSet.addAccountGroup(eMyMoney::Account::Type::Income);
342   //FIXME: port
343   aSet.load(d->ui->m_interestChargeCheckings->ui->m_interestCategoryEdit->selector());
344   aSet.load(d->ui->m_interestChargeCheckings->ui->m_chargesCategoryEdit->selector());
345 
346   // reselect currently selected items
347   if (!payeeId.isEmpty())
348     setField("payeeEdit", payeeId);
349   if (!interestId.isEmpty())
350     setField("interestCategoryEdit", interestId);
351   if (!chargesId.isEmpty())
352     setField("chargesCategoryEdit", chargesId);
353 }
354 
interestTransaction()355 MyMoneyTransaction KEndingBalanceDlg::interestTransaction()
356 {
357   Q_D(KEndingBalanceDlg);
358   return d->m_tInterest;
359 }
360 
chargeTransaction()361 MyMoneyTransaction KEndingBalanceDlg::chargeTransaction()
362 {
363   Q_D(KEndingBalanceDlg);
364   return d->m_tCharges;
365 }
366 
createTransaction(MyMoneyTransaction & t,const int sign,const MyMoneyMoney & amount,const QString & category,const QDate & date)367 bool KEndingBalanceDlg::createTransaction(MyMoneyTransaction &t, const int sign, const MyMoneyMoney& amount, const QString& category, const QDate& date)
368 {
369   Q_D(KEndingBalanceDlg);
370   t = MyMoneyTransaction();
371 
372   if (category.isEmpty() || !date.isValid())
373     return true;
374 
375   MyMoneySplit s1, s2;
376   MyMoneyMoney val = amount * MyMoneyMoney(sign, 1);
377   try {
378     t.setPostDate(date);
379     t.setCommodity(d->m_account.currencyId());
380 
381     s1.setPayeeId(field("payeeEdit").toString());
382     s1.setReconcileFlag(eMyMoney::Split::State::Cleared);
383     s1.setAccountId(d->m_account.id());
384     s1.setValue(-val);
385     s1.setShares(-val);
386 
387     s2 = s1;
388     s2.setAccountId(category);
389     s2.setValue(val);
390 
391     t.addSplit(s1);
392     t.addSplit(s2);
393 
394     QMap<QString, MyMoneyMoney> priceInfo; // just empty
395     MyMoneyMoney shares;
396     if (!KCurrencyCalculator::setupSplitPrice(shares, t, s2, priceInfo, this)) {
397       t = MyMoneyTransaction();
398       return false;
399     }
400 
401     s2.setShares(shares);
402     t.modifySplit(s2);
403 
404   } catch (const MyMoneyException &e) {
405     qDebug("%s", e.what());
406     t = MyMoneyTransaction();
407     return false;
408   }
409 
410   return true;
411 }
412 
help()413 void KEndingBalanceDlg::help()
414 {
415   Q_D(KEndingBalanceDlg);
416   QString anchor = d->m_helpAnchor[currentPage()];
417   if (anchor.isEmpty())
418     anchor = QString("details.reconcile.whatis");
419 
420   KHelpClient::invokeHelp(anchor);
421 }
422 
nextId() const423 int KEndingBalanceDlg::nextId() const
424 {
425   Q_D(const KEndingBalanceDlg);
426   // Starting from the current page, look for the first enabled page
427   // and return that value
428   // If the end of the list is encountered first, then return -1.
429   for (int i = currentId() + 1; i < d->m_pages.size() && i < pageIds().size(); ++i) {
430     if (d->m_pages.testBit(i))
431       return pageIds()[i];
432   }
433   return -1;
434 }
435