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