You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kmymoney/kmymoney2/dialogs/transactioneditor.cpp

2157 lines
79 KiB

/***************************************************************************
transactioneditor.cpp
----------
begin : Wed Jun 07 2006
copyright : (C) 2006 by Thomas Baumgart
email : Thomas Baumgart <ipwizard@users.sourceforge.net>
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
// ----------------------------------------------------------------------------
// QT Includes
#include <tqlabel.h>
#include <tqapplication.h>
#include <tqeventloop.h>
#include <tqradiobutton.h>
#include <tqbuttongroup.h>
#include <tqtooltip.h>
// ----------------------------------------------------------------------------
// KDE Includes
#include <kglobal.h>
#include <ktextedit.h>
#include <klocale.h>
#include <kcombobox.h>
#include <kpushbutton.h>
#include <kmessagebox.h>
#include <kstdguiitem.h>
#include <kiconloader.h>
#include <kguiitem.h>
// ----------------------------------------------------------------------------
// Project Includes
#include <kmymoney/transactioneditor.h>
#include <kmymoney/kmymoneycategory.h>
#include <kmymoney/kmymoneydateinput.h>
#include <kmymoney/kmymoneyedit.h>
#include <kmymoney/kmymoneylineedit.h>
#include <kmymoney/kmymoneyaccountcompletion.h>
#include <kmymoney/kmymoneyaccountselector.h>
#include <kmymoney/mymoneyfile.h>
#include <kmymoney/kmymoneyutils.h>
#include <kmymoney/transactionform.h>
#include <kmymoney/kmymoneyglobalsettings.h>
#include "../dialogs/ksplittransactiondlg.h"
#include "../dialogs/kcurrencycalculator.h"
#include "../dialogs/kselecttransactionsdlg.h"
using namespace KMyMoneyRegister;
using namespace KMyMoneyTransactionForm;
TransactionEditor::TransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::Transaction* item, const KMyMoneyRegister::SelectedTransactions& list, const TQDate& lastPostDate) :
m_transactions(list),
m_regForm(regForm),
m_item(item),
m_transaction(item->transaction()),
m_split(item->split()),
m_lastPostDate(lastPostDate),
m_openEditSplits(false)
{
m_item->startEditMode();
connect(MyMoneyFile::instance(), TQT_SIGNAL(dataChanged()), this, TQT_SLOT(slotUpdateAccount()));
}
TransactionEditor::~TransactionEditor()
{
// Make sure the widgets do not send out signals to the editor anymore
// After all, the editor is about to die
TQMap<TQString, TQWidget*>::iterator it_w;
for(it_w = m_editWidgets.begin(); it_w != m_editWidgets.end(); ++it_w) {
(*it_w)->disconnect(this);
}
m_regForm->removeEditWidgets(m_editWidgets);
m_item->leaveEditMode();
emit finishEdit(m_transactions);
}
void TransactionEditor::slotUpdateAccount(const TQString& id)
{
m_account = MyMoneyFile::instance()->account(id);
setupPrecision();
}
void TransactionEditor::slotUpdateAccount(void)
{
// reload m_account as it might have been changed
m_account = MyMoneyFile::instance()->account(m_account.id());
setupPrecision();
}
void TransactionEditor::setupPrecision(void)
{
const int prec = (m_account.id().isEmpty()) ? 2 : MyMoneyMoney::denomToPrec(m_account.fraction());
TQStringList widgets = TQStringList::split(",", "amount,deposit,payment");
TQStringList::const_iterator it_w;
for(it_w = widgets.begin(); it_w != widgets.end(); ++it_w) {
TQWidget * w;
if((w = haveWidget(*it_w)) != 0) {
dynamic_cast<kMyMoneyEdit*>(w)->setPrecision(prec);
}
}
}
void TransactionEditor::setup(TQWidgetList& tabOrderWidgets, const MyMoneyAccount& account, KMyMoneyRegister::Action action)
{
m_account = account;
m_initialAction = action;
createEditWidgets();
m_regForm->arrangeEditWidgets(m_editWidgets, m_item);
m_regForm->tabOrder(tabOrderWidgets, m_item);
TQWidget* w = haveWidget("tabbar");
if(w)
tabOrderWidgets.append(w);
loadEditWidgets(action);
m_editWidgets.removeOrphans();
clearFinalWidgets();
setupFinalWidgets();
slotUpdateButtonState();
}
void TransactionEditor::clearFinalWidgets(void)
{
m_finalEditWidgets.clear();
}
void TransactionEditor::addFinalWidget(const TQWidget* w)
{
if(w) {
m_finalEditWidgets << w;
}
}
void TransactionEditor::slotReloadEditWidgets(void)
{
}
bool TransactionEditor::eventFilter(TQObject* o, TQEvent* e)
{
bool rc = false;
if(TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(haveWidget("number"))) {
if(e->type() == TQEvent::MouseButtonDblClick) {
emit assignNumber();
rc = true;
}
}
// if the object is a widget, the event is a key press event and
// the object is one of our edit widgets, then ....
if(o->isWidgetType()
&& (e->type() == TQEvent::KeyPress)
&& m_editWidgets.values().contains(dynamic_cast<TQWidget*>(o))) {
TQKeyEvent* k = dynamic_cast<TQKeyEvent*>(e);
if((k->state() & TQt::KeyButtonMask) == 0) {
bool isFinal = false;
TQValueList<const TQWidget*>::const_iterator it_w;
switch(k->key()) {
case TQt::Key_Return:
case TQt::Key_Enter:
// we check, if the object is one of the m_finalEditWidgets and if it's
// a kMyMoneyEdit object that the value is not 0. If any of that is the
// case, it's the final object. In other cases, we convert the enter
// key into a TAB key to move between the fields. Of course, we only need
// to do this as long as the appropriate option is set. In all other cases,
// we treat the return/enter key as such.
if(KMyMoneyGlobalSettings::enterMovesBetweenFields()) {
for(it_w = m_finalEditWidgets.begin(); !isFinal && it_w != m_finalEditWidgets.end(); ++it_w) {
if(TQT_BASE_OBJECT_CONST(*it_w) == TQT_BASE_OBJECT(o)) {
if(dynamic_cast<const kMyMoneyEdit*>(*it_w)) {
isFinal = !(dynamic_cast<const kMyMoneyEdit*>(*it_w)->value().isZero());
} else
isFinal = true;
}
}
} else
isFinal = true;
// for the non-final objects, we treat the return key as a TAB
if(!isFinal) {
TQKeyEvent evt(e->type(),
Key_Tab, 0, k->state(), TQString(),
k->isAutoRepeat(), k->count());
TQApplication::sendEvent( o, &evt );
// in case of a category item and the split button is visible
// send a second event so that we get passed the button.
if(dynamic_cast<KMyMoneyCategory*>(o) && dynamic_cast<KMyMoneyCategory*>(o)->splitButton())
TQApplication::sendEvent( o, &evt );
} else {
TQTimer::singleShot(0, this, TQT_SIGNAL(returnPressed()));
}
// don't process any further
rc = true;
break;
case TQt::Key_Escape:
TQTimer::singleShot(0, this, TQT_SIGNAL(escapePressed()));
break;
}
}
}
return rc;
}
void TransactionEditor::slotNumberChanged(const TQString& txt)
{
kMyMoneyLineEdit* number = dynamic_cast<kMyMoneyLineEdit*>(haveWidget("number"));
if(number) {
if(MyMoneyFile::instance()->checkNoUsed(m_account.id(), txt)) {
if(KMessageBox::questionYesNo(m_regForm, TQString("<qt>")+i18n("The number <b>%1</b> has already been used in account <b>%2</b>. Do you want to replace it with the next available number?").arg(txt).arg(m_account.name())+TQString("</qt>"), i18n("Duplicate number")) == KMessageBox::Yes) {
number->loadText(KMyMoneyUtils::nextCheckNumber(m_account));
}
}
}
}
void TransactionEditor::slotUpdateButtonState(void)
{
TQString reason;
emit transactionDataSufficient(isComplete(reason));
}
TQWidget* TransactionEditor::haveWidget(const TQString& name) const
{
return m_editWidgets.haveWidget(name);
}
int TransactionEditor::slotEditSplits(void)
{
return TQDialog::Rejected;
}
#if 0
// If we deal with multiple currencies we make sure, that for
// transactions with two splits, the transaction's commodity is the
// currency of the currently selected account. This saves us from a
// lot of grieve later on.
// Editing a transaction which has more than two splits and a commodity
// that differs from the currency of the current selected account is
// not a good idea. We will warn the user and give him a hint if there
// is an account where he can perfom the edit operation much better.
if(m_transaction.commodity() != m_account.currencyId()) {
if(m_transaction.splitCount() == 2) {
// in case of two splits, it's easy. We just have to switch the
// transactions commodity. Let's assume the following scenario:
// - transactions commodity is CA
// - account's currencyId is CB
// - second split is of course in CA (otherwise we have a real problem)
// - Value is V in both splits
// - Shares in this account's split is SB
// - Shares in the other account's split is SA (and equal to V)
//
// We do the following:
// - change transactions commodity to CB
// - set V in both splits to SB
// - modify the splits in the transaction
try {
MyMoneySplit split = m_transaction.splitByAccount(m_account.id(), false);
m_transaction.setCommodity(m_account.currencyId());
m_split.setValue(m_split.shares());
split.setValue(-m_split.shares());
m_transaction.modifySplit(m_split);
m_transaction.modifySplit(split);
if(m_transactionPtr) {
KMyMoneyTransaction k(m_transaction);
k.setSplitId(m_split.id());
*m_transactionPtr = k;
}
} catch(MyMoneyException *e) {
tqDebug("Unable to update commodity to second splits currency in %s: '%s'", m_transaction.id().data(), e->what().data());
delete e;
}
} else {
// Find a suitable account
MyMoneySecurity sec = MyMoneyFile::instance()->currency(m_transaction.commodity());
MyMoneyAccount acc;
for(it = m_transaction.splits().begin(); it != m_transaction.splits().end(); ++it) {
if((*it).id() == m_split.id())
continue;
acc = MyMoneyFile::instance()->account((*it).accountId());
if((acc.accountGroup() == MyMoneyAccount::Asset
|| acc.accountGroup() == MyMoneyAccount::Liability)
&& acc.accountType() != MyMoneyAccount::Stock) {
if(m_transaction.commodity() == acc.currencyId())
break;
}
acc = MyMoneyAccount();
}
TQString msg;
msg = TQString("<p>")+i18n("This transaction has more than two splits and is based on a different currency (%1). Using this account to modify the transaction is currently not very well supported by KMyMoney and may result in false results.").arg(sec.name())+TQString(" ");
if(acc.id().isEmpty()) {
msg += i18n("KMyMoney was not able to find a more appropriate account to edit this transaction. Nevertheless, you are allowed to modify the transaction. If you don't want to edit this transaction, please cancel from editing next.");
} else {
msg += i18n("Using e.g. <b>%1</b> to edit this transaction is a better choice. Nevertheless, you are allowed to modify the transaction. If you want to use the suggested account instead, please cancel from editing next and change the view to the suggested account.").arg(acc.name());
}
KMessageBox::information(0, msg);
}
}
#endif
void TransactionEditor::setTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s)
{
m_transaction = t;
m_split = s;
loadEditWidgets();
}
bool TransactionEditor::fixTransactionCommodity(const MyMoneyAccount& account)
{
bool rc = true;
bool firstTimeMultiCurrency = true;
m_account = account;
MyMoneyFile* file = MyMoneyFile::instance();
// determine the max fraction for this account
MyMoneySecurity sec = file->security(m_account.currencyId());
int fract = m_account.fraction();
// scan the list of selected transactions
KMyMoneyRegister::SelectedTransactions::iterator it_t;
for(it_t = m_transactions.begin(); (rc == true) && (it_t != m_transactions.end()); ++it_t) {
// there was a time when the schedule editor did not setup the transaction commodity
// let's give a helping hand here for those old schedules
if((*it_t).transaction().commodity().isEmpty())
(*it_t).transaction().setCommodity(m_account.currencyId());
// we need to check things only if a different commodity is used
if(m_account.currencyId() != (*it_t).transaction().commodity()) {
MyMoneySecurity osec = file->security((*it_t).transaction().commodity());
switch((*it_t).transaction().splitCount()) {
case 0:
// new transaction, guess nothing's here yet ;)
break;
case 1:
try {
// make sure, that the value is equal to the shares, don't forget our own copy
MyMoneySplit& splitB = (*it_t).split(); // reference usage wanted here
if(m_split == splitB)
m_split.setValue(splitB.shares());
splitB.setValue(splitB.shares());
(*it_t).transaction().modifySplit(splitB);
} catch(MyMoneyException *e) {
tqDebug("Unable to update commodity to second splits currency in %s: '%s'", (*it_t).transaction().id().data(), e->what().data());
delete e;
}
break;
case 2:
// If we deal with multiple currencies we make sure, that for
// transactions with two splits, the transaction's commodity is the
// currency of the currently selected account. This saves us from a
// lot of grieve later on. We just have to switch the
// transactions commodity. Let's assume the following scenario:
// - transactions commodity is CA
// - splitB and account's currencyId is CB
// - splitA is of course in CA (otherwise we have a real problem)
// - Value is V in both splits
// - Shares in splitB is SB
// - Shares in splitA is SA (and equal to V)
//
// We do the following:
// - change transactions commodity to CB
// - set V in both splits to SB
// - modify the splits in the transaction
try {
// retrieve the splits
MyMoneySplit& splitB = (*it_t).split(); // reference usage wanted here
MyMoneySplit splitA = (*it_t).transaction().splitByAccount(m_account.id(), false);
// - set V in both splits to SB. Don't forget our own copy
if(m_split == splitB) {
m_split.setValue(splitB.shares());
}
splitB.setValue(splitB.shares());
splitA.setValue(-splitB.shares());
(*it_t).transaction().modifySplit(splitA);
(*it_t).transaction().modifySplit(splitB);
} catch(MyMoneyException *e) {
tqDebug("Unable to update commodity to second splits currency in %s: '%s'", (*it_t).transaction().id().data(), e->what().data());
delete e;
}
break;
default:
// TODO: use new logic by adjusting all splits by the price
// extracted from the selected split. Inform the user that
// this will happen and allow him to stop the processing (rc = false)
try {
TQString msg;
if(firstTimeMultiCurrency) {
firstTimeMultiCurrency = false;
if(!isMultiSelection()) {
msg = i18n("This transaction has more than two splits and is originally based on a different currency (%1). Using this account to modify the transaction may result in rounding errors. Do you want to continue?").arg(osec.name());
} else {
msg = i18n("At least one of the selected transactions has more than two splits and is originally based on a different currency (%1). Using this account to modify the transactions may result in rounding errors. Do you want to continue?").arg(osec.name());
}
if(KMessageBox::warningContinueCancel(0, TQString("<qt>%1</qt>").arg(msg)) == KMessageBox::Cancel) {
rc = false;
}
}
if(rc == true) {
MyMoneyMoney price;
if(!(*it_t).split().shares().isZero() && !(*it_t).split().value().isZero())
price = (*it_t).split().shares() / (*it_t).split().value();
TQValueList<MyMoneySplit>::iterator it_s;
MyMoneySplit& mySplit = (*it_t).split();
for(it_s = (*it_t).transaction().splits().begin(); it_s != (*it_t).transaction().splits().end(); ++it_s) {
MyMoneySplit s = (*it_s);
if(s == mySplit) {
s.setValue(s.shares());
if(mySplit == m_split) {
m_split = s;
}
mySplit = s;
} else {
s.setValue((s.value() * price).convert(fract));
}
(*it_t).transaction().modifySplit(s);
}
}
} catch(MyMoneyException *e) {
tqDebug("Unable to update commodity of split currency in %s: '%s'", (*it_t).transaction().id().data(), e->what().data());
delete e;
}
break;
}
// set the transaction's ommodity to this account's currency
(*it_t).transaction().setCommodity(m_account.currencyId());
// update our copy of the transaction that has the focus
if((*it_t).transaction().id() == m_transaction.id()) {
m_transaction = (*it_t).transaction();
}
}
}
return rc;
}
void TransactionEditor::assignNextNumber(void)
{
if(canAssignNumber()) {
kMyMoneyLineEdit* number = dynamic_cast<kMyMoneyLineEdit*>(haveWidget("number"));
number->loadText(KMyMoneyUtils::nextCheckNumber(m_account));
}
}
bool TransactionEditor::canAssignNumber(void) const
{
kMyMoneyLineEdit* number = dynamic_cast<kMyMoneyLineEdit*>(haveWidget("number"));
return (number != 0) && (number->text().isEmpty());
}
void TransactionEditor::setupCategoryWidget(KMyMoneyCategory* category, const TQValueList<MyMoneySplit>& splits, TQString& categoryId, const char* splitEditSlot, bool /* allowObjectCreation */)
{
disconnect(category, TQT_SIGNAL(focusIn()), this, splitEditSlot);
#if 0
// FIXME must deal with the logic that suppressObjectCreation is
// automatically turned off when the createItem() signal is connected
if(allowObjectCreation)
category->setSuppressObjectCreation(false);
#endif
switch(splits.count()) {
case 0:
categoryId = TQString();
if(!category->currentText().isEmpty()) {
category->setCurrentText();
// make sure, we don't see the selector
category->completion()->hide();
}
category->completion()->setSelected(TQString());
break;
case 1:
categoryId = splits[0].accountId();
category->completion()->setSelected(categoryId);
category->slotItemSelected(categoryId);
break;
default:
categoryId = TQString();
category->setSplitTransaction();
connect(category, TQT_SIGNAL(focusIn()), this, splitEditSlot);
#if 0
// FIXME must deal with the logic that suppressObjectCreation is
// automatically turned off when the createItem() signal is connected
if(allowObjectCreation)
category->setSuppressObjectCreation(true);
#endif
break;
}
}
bool TransactionEditor::enterTransactions(TQString& newId, bool askForSchedule, bool suppressBalanceWarnings)
{
newId = TQString();
MyMoneyFile* file = MyMoneyFile::instance();
// make sure to run through all stuff that is tied to 'focusout events'.
m_regForm->parentWidget()->setFocus();
TQApplication::eventLoop()->processEvents(TQEventLoop::ExcludeUserInput, 10);
// we don't need to update our widgets anymore, so we just disconnect the signal
disconnect(file, TQT_SIGNAL(dataChanged()), this, TQT_SLOT(slotReloadEditWidgets()));
KMyMoneyRegister::SelectedTransactions::iterator it_t;
MyMoneyTransaction t;
bool newTransactionCreated = false;
// make sure, that only a single new transaction can be created.
// we need to update m_transactions to contain the new transaction
// which is then stored in the variable t when we leave the loop.
// m_transactions will be sent out in finishEdit() and forces
// the new transaction to be selected in the ledger view
// collect the transactions to be stored in the engine in a local
// list first, so that the user has a chance to interrupt the storage
// process
TQValueList<MyMoneyTransaction> list;
bool storeTransactions = true;
// collect transactions
for(it_t = m_transactions.begin(); storeTransactions && !newTransactionCreated && it_t != m_transactions.end(); ++it_t) {
storeTransactions = createTransaction(t, (*it_t).transaction(), (*it_t).split());
// if the transaction was created successfully, append it to the list
if(storeTransactions)
list.append(t);
// if we created a new transaction keep that in mind
if(t.id().isEmpty())
newTransactionCreated = true;
}
// if not interrupted by user, continue to store them in the engine
if(storeTransactions) {
int i = 0;
emit statusMsg(i18n("Storing transactions"));
emit statusProgress(0, list.count());
MyMoneyFileTransaction ft;
try {
TQValueList<MyMoneyTransaction>::iterator it_ts;
TQMap<TQString, bool> minBalanceEarly;
TQMap<TQString, bool> minBalanceAbsolute;
TQMap<TQString, bool> maxCreditEarly;
TQMap<TQString, bool> maxCreditAbsolute;
TQMap<TQString, bool> accountIds;
for(it_ts = list.begin(); it_ts != list.end(); ++it_ts) {
// if we have a categorization, make sure we remove
// the 'imported' flag automagically
if((*it_ts).splitCount() > 1)
(*it_ts).setImported(false);
// create information about min and max balances
TQValueList<MyMoneySplit>::const_iterator it_s;
for(it_s = (*it_ts).splits().begin(); it_s != (*it_ts).splits().end(); ++it_s) {
MyMoneyAccount acc = file->account((*it_s).accountId());
accountIds[acc.id()] = true;
MyMoneyMoney balance = file->balance(acc.id());
if(!acc.value("minBalanceEarly").isEmpty()) {
minBalanceEarly[acc.id()] = balance < MyMoneyMoney(acc.value("minBalanceEarly"));
}
if(!acc.value("minBalanceAbsolute").isEmpty()) {
minBalanceAbsolute[acc.id()] = balance < MyMoneyMoney(acc.value("minBalanceAbsolute"));
minBalanceEarly[acc.id()] = false;
}
if(!acc.value("maxCreditEarly").isEmpty()) {
maxCreditEarly[acc.id()] = balance < MyMoneyMoney(acc.value("maxCreditEarly"));
}
if(!acc.value("maxCreditAbsolute").isEmpty()) {
maxCreditAbsolute[acc.id()] = balance < MyMoneyMoney(acc.value("maxCreditAbsolute"));
maxCreditEarly[acc.id()] = false;
}
}
if((*it_ts).id().isEmpty()) {
bool enter = true;
if(askForSchedule && (*it_ts).postDate() > TQDate::currentDate()) {
KGuiItem enterItem;
KIconLoader* il = TDEGlobal::iconLoader();
KGuiItem enterButton( i18n("&Enter" ),
TQIconSet(il->loadIcon("kontact_journal", KIcon::Small, KIcon::SizeSmall)),
i18n("Accepts the entered data and stores it"),
i18n("Use this to enter the transaction into the ledger."));
KGuiItem scheduleButton( i18n("&Schedule" ),
TQIconSet(il->loadIcon("kontact_date", KIcon::Small, KIcon::SizeSmall)),
i18n("Accepts the entered data and stores it as schedule"),
i18n("Use this to schedule the transaction for later entry into the ledger."));
enter = KMessageBox::questionYesNo(m_regForm, TQString("<qt>%1</qt>").arg(i18n("The transaction you are about to enter has a post date in the future.<br/><br/>Do you want to enter it in the ledger or add it to the schedules?")), i18n("Dialog caption for 'Enter or schedule' dialog", "Enter or schedule?"), enterButton, scheduleButton, "EnterOrScheduleTransactionInFuture") == KMessageBox::Yes;
}
if(enter) {
// add new transaction
file->addTransaction(*it_ts);
// pass the newly assigned id on to the caller
newId = (*it_ts).id();
// refresh account and transaction object because they might have changed
m_account = file->account(m_account.id());
t = (*it_ts);
// if a new transaction has a valid number, keep it with the account
TQString number = (*it_ts).splits()[0].number();
if(!number.isEmpty()) {
m_account.setValue("lastNumberUsed", number);
file->modifyAccount(m_account);
}
} else {
// turn object creation on, so that moving the focus does
// not screw up the dialog that might be popping up
emit objectCreation(true);
emit scheduleTransaction(*it_ts, MyMoneySchedule::OCCUR_ONCE);
emit objectCreation(false);
newTransactionCreated = false;
}
// send out the post date of this transaction
emit lastPostDateUsed((*it_ts).postDate());
} else {
// modify existing transaction
file->modifyTransaction(*it_ts);
}
}
emit statusProgress(i++, 0);
// update m_transactions to contain the newly created transaction so that
// it is selected as the current one
// we need to do that before we commit the transaction to the engine
// as we need it during the update of the views that is caused by committing already.
if(newTransactionCreated) {
m_transactions.clear();
MyMoneySplit s;
// a transaction w/o a single split should not exist and adding it
// should throw an exception in MyMoneyFile::addTransaction, but we
// remain on the save side of things to check for it
if(t.splitCount() > 0)
s = t.splits()[0];
KMyMoneyRegister::SelectedTransaction st(t, s);
m_transactions.append(st);
}
ft.commit();
// now analyse the balances and spit out warnings to the user
TQMap<TQString, bool>::const_iterator it_a;
if(!suppressBalanceWarnings) {
for(it_a = accountIds.begin(); it_a != accountIds.end(); ++it_a) {
TQString msg;
MyMoneyAccount acc = file->account(it_a.key());
MyMoneyMoney balance = file->balance(acc.id());
const MyMoneySecurity& sec = file->security(acc.currencyId());
TQString key;
key = "minBalanceEarly";
if(!acc.value(key).isEmpty()) {
if(minBalanceEarly[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
msg = TQString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the warning balance of %2.").arg(acc.name(), MyMoneyMoney(acc.value(key)).formatMoney(acc, sec)));
}
}
key = "minBalanceAbsolute";
if(!acc.value(key).isEmpty()) {
if(minBalanceAbsolute[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
msg = TQString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the minimum balance of %2.").arg(acc.name(), MyMoneyMoney(acc.value(key)).formatMoney(acc, sec)));
}
}
key = "maxCreditEarly";
if(!acc.value(key).isEmpty()) {
if(maxCreditEarly[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
msg = TQString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the maximum credit warning limit of %2.").arg(acc.name(), MyMoneyMoney(acc.value(key)).formatMoney(acc, sec)));
}
}
key = "maxCreditAbsolute";
if(!acc.value(key).isEmpty()) {
if(maxCreditAbsolute[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) {
msg = TQString("<qt>%1</qt>").arg(i18n("The balance of account <b>%1</b> dropped below the maximum credit limit of %2.").arg(acc.name(), MyMoneyMoney(acc.value(key)).formatMoney(acc, sec)));
}
}
if(!msg.isEmpty()) {
emit balanceWarning(m_regForm, acc, msg);
}
}
}
} catch(MyMoneyException * e) {
tqDebug("Unable to store transaction within engine: %s", e->what().latin1());
delete e;
newTransactionCreated = false;
}
emit statusProgress(-1, -1);
emit statusMsg(TQString());
}
return storeTransactions;
}
StdTransactionEditor::StdTransactionEditor()
{
}
StdTransactionEditor::StdTransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::Transaction* item, const KMyMoneyRegister::SelectedTransactions& list, const TQDate& lastPostDate) :
TransactionEditor(regForm, item, list, lastPostDate),
m_inUpdateVat(false)
{
}
StdTransactionEditor::~StdTransactionEditor()
{
KMyMoneyTransactionForm::TransactionForm* form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(m_regForm);
if(form) {
form->enableTabBar(true);
}
}
bool StdTransactionEditor::eventFilter(TQObject* o, TQEvent* e)
{
bool rc = TransactionEditor::eventFilter(o, e);
#if 0
// this is sofar dead code here, as the focus out event for Comboboxes
// never comes along here. I don't know why (ipwizard - 2009-10-03)
if((e->type() == TQEvent::FocusOut)
&& (haveWidget("payee") == dynamic_cast<TQWidget*>(o))) {
// loosing the focus on the payee widget?
tqDebug("Loosing focus on payee");
KMyMoneyPayeeCombo* p = dynamic_cast<KMyMoneyPayeeCombo*>(haveWidget("payee"));
if(!p->selectedItem().isEmpty())
slotUpdatePayee(p->selectedItem());
}
#endif
return rc;
}
void StdTransactionEditor::createEditWidgets(void)
{
KMyMoneyCategory* account = new KMyMoneyCategory;
account->setHint(i18n("Account"));
m_editWidgets["account"] = account;
connect(account, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotUpdateButtonState()));
connect(account, TQT_SIGNAL(itemSelected(const TQString&)), this, TQT_SLOT(slotUpdateAccount(const TQString&)));
KMyMoneyPayeeCombo* payee = new KMyMoneyPayeeCombo;
payee->setHint(i18n("Payer/Receiver"));
m_editWidgets["payee"] = payee;
connect(payee, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotUpdateButtonState()));
connect(payee, TQT_SIGNAL(createItem(const TQString&, TQString&)), this, TQT_SIGNAL(createPayee(const TQString&, TQString&)));
connect(payee, TQT_SIGNAL(objectCreation(bool)), this, TQT_SIGNAL(objectCreation(bool)));
connect(payee, TQT_SIGNAL(itemSelected(const TQString&)), this, TQT_SLOT(slotUpdatePayee(const TQString&)));
KMyMoneyCategory* category = new KMyMoneyCategory(0, 0, true);
category->setHint(i18n("Category/Account"));
m_editWidgets["category"] = category;
connect(category, TQT_SIGNAL(itemSelected(const TQString&)), this, TQT_SLOT(slotUpdateCategory(const TQString&)));
connect(category, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotUpdateButtonState()));
connect(category, TQT_SIGNAL(createItem(const TQString&, TQString&)), this, TQT_SLOT(slotCreateCategory(const TQString&, TQString&)));
connect(category, TQT_SIGNAL(objectCreation(bool)), this, TQT_SIGNAL(objectCreation(bool)));
connect(category->splitButton(), TQT_SIGNAL(clicked()), this, TQT_SLOT(slotEditSplits()));
category->splitButton()->setDisabled(true);
KTextEdit* memo = new KTextEdit;
memo->setTabChangesFocus(true);
m_editWidgets["memo"] = memo;
bool showNumberField = true;
switch(m_account.accountType()) {
case MyMoneyAccount::Savings:
case MyMoneyAccount::Cash:
case MyMoneyAccount::Loan:
case MyMoneyAccount::AssetLoan:
case MyMoneyAccount::Asset:
case MyMoneyAccount::Liability:
case MyMoneyAccount::Equity:
showNumberField = KMyMoneyGlobalSettings::alwaysShowNrField();
break;
case MyMoneyAccount::Income:
case MyMoneyAccount::Expense:
showNumberField = false;
break;
default:
break;
}
if(showNumberField) {
kMyMoneyLineEdit* number = new kMyMoneyLineEdit;
number->setHint(i18n("Number"));
m_editWidgets["number"] = number;
connect(number, TQT_SIGNAL(lineChanged(const TQString&)), this, TQT_SLOT(slotNumberChanged(const TQString&)));
// number->installEventFilter(this);
}
m_editWidgets["postdate"] = new kMyMoneyDateInput;
connect(m_editWidgets["postdate"], TQT_SIGNAL(dateChanged(const TQDate&)), this, TQT_SLOT(slotUpdateButtonState()));
kMyMoneyEdit* value = new kMyMoneyEdit;
m_editWidgets["amount"] = value;
value->setResetButtonVisible(false);
connect(value, TQT_SIGNAL(valueChanged(const TQString&)), this, TQT_SLOT(slotUpdateAmount(const TQString&)));
connect(value, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotUpdateButtonState()));
value = new kMyMoneyEdit;
m_editWidgets["payment"] = value;
value->setResetButtonVisible(false);
connect(value, TQT_SIGNAL(valueChanged(const TQString&)), this, TQT_SLOT(slotUpdatePayment(const TQString&)));
connect(value, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotUpdateButtonState()));
value = new kMyMoneyEdit;
m_editWidgets["deposit"] = value;
value->setResetButtonVisible(false);
connect(value, TQT_SIGNAL(valueChanged(const TQString&)), this, TQT_SLOT(slotUpdateDeposit(const TQString&)));
connect(value, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(slotUpdateButtonState()));
KMyMoneyCashFlowCombo* cashflow = new KMyMoneyCashFlowCombo(0, 0, m_account.accountGroup());
m_editWidgets["cashflow"] = cashflow;
connect(cashflow, TQT_SIGNAL(directionSelected(KMyMoneyRegister::CashFlowDirection)), this, TQT_SLOT(slotUpdateCashFlow(KMyMoneyRegister::CashFlowDirection)));
connect(cashflow, TQT_SIGNAL(directionSelected(KMyMoneyRegister::CashFlowDirection)), this, TQT_SLOT(slotUpdateButtonState()));
KMyMoneyReconcileCombo* reconcile = new KMyMoneyReconcileCombo;
m_editWidgets["status"] = reconcile;
connect(reconcile, TQT_SIGNAL(itemSelected(const TQString&)), this, TQT_SLOT(slotUpdateButtonState()));
KMyMoneyRegister::TQWidgetContainer::iterator it_w;
for(it_w = m_editWidgets.begin(); it_w != m_editWidgets.end(); ++it_w) {
(*it_w)->installEventFilter(this);
}
// if we don't have more than 1 selected transaction, we don't need
// the "don't change" item in some of the combo widgets
if(!isMultiSelection()) {
reconcile->removeDontCare();
cashflow->removeDontCare();
}
TQLabel* label;
m_editWidgets["category-label"] = label = new TQLabel(i18n("Category"), 0);
label->setAlignment(TQt::AlignVCenter | TQt::DontClip);
// create a copy of tabbar above the form (if we are created for a form)
KMyMoneyTransactionForm::TransactionForm* form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(m_regForm);
if(form) {
form->enableTabBar(false);
KMyMoneyTransactionForm::TabBar* tabbar = new KMyMoneyTransactionForm::TabBar;
m_editWidgets["tabbar"] = tabbar;
tabbar->copyTabs(form->tabBar());
connect(tabbar, TQT_SIGNAL(tabSelected(int)), this, TQT_SLOT(slotUpdateAction(int)));
}
label = new TQLabel(i18n("Date"), 0);
label->setAlignment(TQt::AlignVCenter | TQt::DontClip);
m_editWidgets["date-label"] = label;
label = new TQLabel(i18n("Number"), 0);
label->setAlignment(TQt::AlignVCenter | TQt::DontClip);
m_editWidgets["number-label"] = label;
setupPrecision();
}
void StdTransactionEditor::setupCategoryWidget(TQString& categoryId)
{
TransactionEditor::setupCategoryWidget(dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]), m_splits, categoryId, TQT_SLOT(slotEditSplits()));
if(m_splits.count() == 1)
m_shares = m_splits[0].shares();
}
bool StdTransactionEditor::isTransfer(const TQString& accId1, const TQString& accId2) const
{
if(accId1.isEmpty() || accId2.isEmpty())
return false;
return MyMoneyFile::instance()->account(accId1).isIncomeExpense() == MyMoneyFile::instance()->account(accId2).isIncomeExpense();
}
void StdTransactionEditor::loadEditWidgets(KMyMoneyRegister::Action action)
{
// don't kick off VAT processing from here
m_inUpdateVat = true;
TQMap<TQString, TQWidget*>::const_iterator it_w;
TQWidget* w;
AccountSet aSet;
// load the account widget
KMyMoneyCategory* account = dynamic_cast<KMyMoneyCategory*>(haveWidget("account"));
if(account) {
aSet.addAccountGroup(MyMoneyAccount::Asset);
aSet.addAccountGroup(MyMoneyAccount::Liability);
aSet.removeAccountType(MyMoneyAccount::AssetLoan);
aSet.removeAccountType(MyMoneyAccount::CertificateDep);
aSet.removeAccountType(MyMoneyAccount::Investment);
aSet.removeAccountType(MyMoneyAccount::Stock);
aSet.removeAccountType(MyMoneyAccount::MoneyMarket);
aSet.removeAccountType(MyMoneyAccount::Loan);
aSet.load(account->selector());
account->completion()->setSelected(m_account.id());
account->slotItemSelected(m_account.id());
}
// load the payee widget
KMyMoneyPayeeCombo* payee = dynamic_cast<KMyMoneyPayeeCombo*>(m_editWidgets["payee"]);
payee->loadPayees(MyMoneyFile::instance()->payeeList());
// load the category widget
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
disconnect(category, TQT_SIGNAL(focusIn()), this, TQT_SLOT(slotEditSplits()));
// check if the current transaction has a reference to an equity account
bool haveEquityAccount = false;
TQValueList<MyMoneySplit>::const_iterator it_s;
for(it_s = m_transaction.splits().begin(); !haveEquityAccount && it_s != m_transaction.splits().end(); ++it_s) {
MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
if(acc.accountType() == MyMoneyAccount::Equity)
haveEquityAccount = true;
}
aSet.clear();
aSet.addAccountGroup(MyMoneyAccount::Asset);
aSet.addAccountGroup(MyMoneyAccount::Liability);
aSet.addAccountGroup(MyMoneyAccount::Income);
aSet.addAccountGroup(MyMoneyAccount::Expense);
if(KMyMoneyGlobalSettings::expertMode() || haveEquityAccount)
aSet.addAccountGroup(MyMoneyAccount::Equity);
aSet.removeAccountType(MyMoneyAccount::CertificateDep);
aSet.removeAccountType(MyMoneyAccount::Investment);
aSet.removeAccountType(MyMoneyAccount::Stock);
aSet.removeAccountType(MyMoneyAccount::MoneyMarket);
aSet.load(category->selector());
// if an account is specified then remove it from the widget so that the user
// cannot create a transfer with from and to account being the same account
if(!m_account.id().isEmpty())
category->selector()->removeItem(m_account.id());
if(!isMultiSelection()) {
dynamic_cast<KTextEdit*>(m_editWidgets["memo"])->setText(m_split.memo());
if(m_transaction.postDate().isValid())
dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->setDate(m_transaction.postDate());
else if(m_lastPostDate.isValid())
dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->setDate(m_lastPostDate);
else
dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->setDate(TQDate::currentDate());
if((w = haveWidget("number")) != 0) {
dynamic_cast<kMyMoneyLineEdit*>(w)->loadText(m_split.number());
if(m_transaction.id().isEmpty() // new transaction
&& dynamic_cast<kMyMoneyLineEdit*>(w)->text().isEmpty() // no number filled in
&& m_account.accountType() == MyMoneyAccount::Checkings // checkings account
&& KMyMoneyGlobalSettings::autoIncCheckNumber()) { // and auto inc number turned on?
assignNextNumber();
}
}
dynamic_cast<KMyMoneyReconcileCombo*>(m_editWidgets["status"])->setState(m_split.reconcileFlag());
TQString payeeId = m_split.payeeId();
if(!payeeId.isEmpty()) {
payee->setSelectedItem(payeeId);
}
m_splits.clear();
if(m_transaction.splitCount() < 2) {
category->completion()->setSelected(TQString());
} else {
TQValueList<MyMoneySplit>::const_iterator it_s;
for(it_s = m_transaction.splits().begin(); it_s != m_transaction.splits().end(); ++it_s) {
if((*it_s) == m_split)
continue;
m_splits.append(*it_s);
}
}
TQString categoryId;
setupCategoryWidget(categoryId);
if((w = haveWidget("cashflow")) != 0) {
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(w);
cashflow->setDirection(m_split.value().isNegative() ? KMyMoneyRegister::Payment : KMyMoneyRegister::Deposit);
}
if((w = haveWidget("category-label")) != 0) {
TQLabel *categoryLabel = dynamic_cast<TQLabel*>(w);
if(isTransfer(m_split.accountId(), categoryId)) {
if(m_split.value().isPositive())
categoryLabel->setText(i18n("Transfer from"));
else
categoryLabel->setText(i18n("Transfer to"));
}
}
MyMoneyMoney value = m_split.shares();
if(haveWidget("deposit")) {
if(m_split.shares().isNegative()) {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->loadText("");
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->setValue(value.abs());
} else {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->setValue(value.abs());
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->loadText("");
}
}
if((w = haveWidget("amount")) != 0) {
dynamic_cast<kMyMoneyEdit*>(w)->setValue(value.abs());
}
slotUpdateCategory(categoryId);
// try to preset for specific action if a new transaction is being started
if(m_transaction.id().isEmpty()) {
if((w = haveWidget("category-label")) != 0) {
TabBar* tabbar = dynamic_cast<TabBar*>(haveWidget("tabbar"));
if(action == KMyMoneyRegister::ActionNone) {
if(tabbar) {
action = static_cast<KMyMoneyRegister::Action>(tabbar->currentTab());
}
}
if(action != KMyMoneyRegister::ActionNone) {
TQLabel *categoryLabel = dynamic_cast<TQLabel*>(w);
if(action == KMyMoneyRegister::ActionTransfer) {
if(m_split.value().isPositive())
categoryLabel->setText(i18n("Transfer from"));
else
categoryLabel->setText(i18n("Transfer to"));
}
if((w = haveWidget("cashflow")) != 0) {
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(w);
if(action == KMyMoneyRegister::ActionDeposit || (action == KMyMoneyRegister::ActionTransfer && m_split.value().isPositive()))
cashflow->setDirection(KMyMoneyRegister::Deposit);
else
cashflow->setDirection(KMyMoneyRegister::Payment);
}
if(tabbar) {
tabbar->setCurrentTab(action);
}
}
}
} else {
TabBar* tabbar = dynamic_cast<TabBar*>(haveWidget("tabbar"));
if(tabbar) {
if(!isTransfer(m_split.accountId(), categoryId)) {
tabbar->setCurrentTab(m_split.value().isNegative() ? KMyMoneyRegister::ActionWithdrawal : KMyMoneyRegister::ActionDeposit);
} else {
tabbar->setCurrentTab(KMyMoneyRegister::ActionTransfer);
}
}
}
} else {
dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->loadDate(TQDate());
dynamic_cast<KMyMoneyReconcileCombo*>(m_editWidgets["status"])->setState(MyMoneySplit::Unknown);
if(haveWidget("deposit")) {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->loadText("");
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->setAllowEmpty();
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->loadText("");
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->setAllowEmpty();
}
if((w = haveWidget("amount")) != 0) {
dynamic_cast<kMyMoneyEdit*>(w)->loadText("");
dynamic_cast<kMyMoneyEdit*>(w)->setAllowEmpty();
}
if((w = haveWidget("cashflow")) != 0) {
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(w);
cashflow->setDirection(KMyMoneyRegister::Unknown);
}
if((w = haveWidget("tabbar")) != 0) {
w->setEnabled(false);
}
category->completion()->setSelected(TQString());
}
// allow kick off VAT processing again
m_inUpdateVat = false;
}
TQWidget* StdTransactionEditor::firstWidget(void) const
{
TQWidget* w = 0;
if(m_initialAction != KMyMoneyRegister::ActionNone)
w = haveWidget("payee");
return w;
}
void StdTransactionEditor::slotReloadEditWidgets(void)
{
// reload category widget
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
TQString categoryId = category->selectedItem();
AccountSet aSet;
aSet.addAccountGroup(MyMoneyAccount::Asset);
aSet.addAccountGroup(MyMoneyAccount::Liability);
aSet.addAccountGroup(MyMoneyAccount::Income);
aSet.addAccountGroup(MyMoneyAccount::Expense);
if(KMyMoneyGlobalSettings::expertMode())
aSet.addAccountGroup(MyMoneyAccount::Equity);
aSet.load(category->selector());
// if an account is specified then remove it from the widget so that the user
// cannot create a transfer with from and to account being the same account
if(!m_account.id().isEmpty())
category->selector()->removeItem(m_account.id());
if(!categoryId.isEmpty())
category->setSelectedItem(categoryId);
// reload payee widget
KMyMoneyPayeeCombo* payee = dynamic_cast<KMyMoneyPayeeCombo*>(m_editWidgets["payee"]);
TQString payeeId = payee->selectedItem();
payee->loadPayees(MyMoneyFile::instance()->payeeList());
if(!payeeId.isEmpty()) {
payee->setSelectedItem(payeeId);
}
}
void StdTransactionEditor::slotUpdatePayee(const TQString& payeeId)
{
// we have a new payee assigned to this transaction.
// retrieve some information about the state of the category widget
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
TQStringList list;
category->selectedItems(list);
// If payee has associated default account (category), set that now if
// category is not filled
const MyMoneyPayee& payeeObj = MyMoneyFile::instance()->payee(payeeId);
if (payeeObj.defaultAccountEnabled() && list.isEmpty()) {
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
category->slotItemSelected(payeeObj.defaultAccountId());
return;
}
// in case there is no category assigned, no value entered and no
// memo available, we search for the last transaction of this payee
// in the account.
if(m_transaction.id().isEmpty()
&& m_splits.count() == 0
&& KMyMoneyGlobalSettings::autoFillTransaction() != 0
&& list.isEmpty()) {
// check if memo is empty
KTextEdit* memo = dynamic_cast<KTextEdit*>(m_editWidgets["memo"]);
if(memo && !memo->text().isEmpty())
return;
// check if all value fields are empty
kMyMoneyEdit* amount;
TQStringList fields;
fields << "amount" << "payment" << "deposit";
TQStringList::const_iterator it_f;
for(it_f = fields.begin(); it_f != fields.end(); ++it_f) {
amount = dynamic_cast<kMyMoneyEdit*>(haveWidget(*it_f));
if(amount && !amount->value().isZero())
return;
}
#if 0
// Tony mentioned, that autofill does not work when he changed the date. Well,
// that certainly makes sense when you enter transactions in register mode as
// opposed to form mode, because the date field is located prior to the date
// field in the tab order of the widgets and the user might have already
// changed it.
//
// So I commented out the code that checks the date but left it in for reference.
// (ipwizard, 2008-04-07)
// check if date has been altered by user
kMyMoneyDateInput* postDate = dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"]);
if((m_lastPostDate.isValid() && (postDate->date() != m_lastPostDate))
|| (!m_lastPostDate.isValid() && (postDate->date() != TQDate::currentDate())))
return;
#endif
// if we got here, we have to autofill
autoFill(payeeId);
}
}
MyMoneyMoney StdTransactionEditor::shares(const MyMoneyTransaction& t) const
{
MyMoneyMoney result;
TQValueList<MyMoneySplit>::const_iterator it_s;
for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
if((*it_s).accountId() == m_account.id()) {
result += (*it_s).shares();
}
}
return result;
}
struct uniqTransaction {
const MyMoneyTransaction* t;
int cnt;
};
void StdTransactionEditor::autoFill(const TQString& payeeId)
{
TQValueList<TQPair<MyMoneyTransaction, MyMoneySplit> > list;
MyMoneyTransactionFilter filter(m_account.id());
filter.addPayee(payeeId);
MyMoneyFile::instance()->transactionList(list, filter);
if(!list.empty()) {
// ok, we found at least one previous transaction. now we clear out
// what we have collected so far and add those splits from
// the previous transaction.
TQValueList<TQPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it_t;
TQMap<TQString, struct uniqTransaction> uniqList;
// collect the transactions and see if we have any duplicates
for(it_t = list.begin(); it_t != list.end(); ++it_t) {
TQString key = (*it_t).first.accountSignature();
int cnt = 0;
TQMap<TQString, struct uniqTransaction>::iterator it_u;
do {
TQString ukey = TQString("%1-%2").arg(key).arg(cnt);
it_u = uniqList.find(ukey);
if(it_u == uniqList.end()) {
uniqList[ukey].t = &((*it_t).first);
uniqList[ukey].cnt = 1;
} else if (KMyMoneyGlobalSettings::autoFillTransaction() == 1) {
// we already have a transaction with this signature. we must
// now check, if we should really treat it as a duplicate according
// to the value comparison delta.
MyMoneyMoney s1 = shares(*((*it_u).t));
MyMoneyMoney s2 = shares((*it_t).first);
if(s2.abs() > s1.abs()) {
MyMoneyMoney t(s1);
s1 = s2;
s2 = t;
}
MyMoneyMoney diff;
if(s2.isZero())
diff = s1.abs();
else
diff = ((s1 - s2) / s2).convert(10000);
if(diff.isPositive() && diff <= MyMoneyMoney(KMyMoneyGlobalSettings::autoFillDifference(),100)) {
uniqList[ukey].t = &((*it_t).first);
break; // end while loop
}
} else if (KMyMoneyGlobalSettings::autoFillTransaction() == 2) {
(*it_u).cnt++;
break; // end while loop
}
++cnt;
} while(it_u != uniqList.end());
}
MyMoneyTransaction t;
if (KMyMoneyGlobalSettings::autoFillTransaction() != 2) {
#if 0
// I removed this code to allow cancellation of an autofill if
// it does not match even if there is only a single matching
// transaction for the payee in question. In case, we want to revert
// to the old behavior, don't forget to uncomment the closing
// brace further down in the code as well. (ipwizard 2009-01-16)
if(uniqList.count() == 1) {
t = list.last().first;
} else {
#endif
TDESelectTransactionsDlg dlg(m_account, m_regForm);
dlg.setCaption(i18n("Select autofill transaction"));
TQMap<TQString, struct uniqTransaction>::const_iterator it_u;
for(it_u = uniqList.begin(); it_u != uniqList.end(); ++it_u) {
dlg.addTransaction(*(*it_u).t);
}
// setup sort order
dlg.m_register->setSortOrder("1,-9,-4");
// sort the transactions according to the sort setting
dlg.m_register->sortItems();
// and select the last item
if(dlg.m_register->lastItem())
dlg.m_register->selectItem(dlg.m_register->lastItem());
if(dlg.exec() == TQDialog::Accepted) {
t = dlg.transaction();
}
#if 0
}
#endif
} else {
int maxCnt = 0;
TQMap<TQString, struct uniqTransaction>::const_iterator it_u;
for(it_u = uniqList.begin(); it_u != uniqList.end(); ++it_u) {
if((*it_u).cnt > maxCnt) {
t = *(*it_u).t;
maxCnt = (*it_u).cnt;
}
}
}
if(t != MyMoneyTransaction()) {
m_transaction.removeSplits();
m_split = MyMoneySplit();
MyMoneySplit otherSplit;
TQValueList<MyMoneySplit>::ConstIterator it;
for(it = t.splits().begin(); it != t.splits().end(); ++it) {
MyMoneySplit s(*it);
s.setReconcileFlag(MyMoneySplit::NotReconciled);
s.setReconcileDate(TQDate());
s.clearId();
s.setBankID(TQString());
// older versions of KMyMoney used to set the action
// we don't need this anymore
if(s.action() != MyMoneySplit::ActionAmortization
&& s.action() != MyMoneySplit::ActionInterest) {
s.setAction(TQString());
}
// FIXME update check number. The old comment contained
//
// <quote>
// If a check number is already specified by the user it is
// used. If the input field is empty and the previous transaction
// contains a checknumber, the next usuable check no will be assigned
// to the transaction.
// </quote>
kMyMoneyLineEdit* editNr = dynamic_cast<kMyMoneyLineEdit*>(haveWidget("number"));
if(editNr && !editNr->text().isEmpty()) {
s.setNumber(editNr->text());
} else if(!s.number().isEmpty()) {
s.setNumber(KMyMoneyUtils::nextCheckNumber(m_account));
}
// if the transaction has exactly two splits, remove
// the memo text of the split that does not reference
// the current account. This allows the user to change
// the autofilled memo text which will then also be used
// in this split. See createTransaction() for this logic.
if(s.accountId() != m_account.id() && t.splitCount() == 2)
s.setMemo(TQString());
m_transaction.addSplit(s);
if(s.accountId() == m_account.id() && m_split == MyMoneySplit()) {
m_split = s;
} else {
otherSplit = s;
}
}
// make sure to extract the right action
KMyMoneyRegister::Action action;
action = m_split.shares().isNegative() ? KMyMoneyRegister::ActionWithdrawal : KMyMoneyRegister::ActionDeposit;
if(m_transaction.splitCount() == 2) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(otherSplit.accountId());
if(acc.isAssetLiability())
action = KMyMoneyRegister::ActionTransfer;
}
// now setup the widgets with the new data but keep the date
TQDate date = dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->date();
loadEditWidgets(action);
dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"])->setDate(date);
}
}
// focus jumps into the category field
TQWidget* w;
if((w = haveWidget("payee")) != 0) {
w->setFocus();
}
}
void StdTransactionEditor::slotUpdateAction(int action)
{
TabBar* tabbar = dynamic_cast<TabBar*>(haveWidget("tabbar"));
if(tabbar) {
TQLabel* categoryLabel = dynamic_cast<TQLabel*>(haveWidget("category-label"));
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(m_editWidgets["cashflow"]);
switch(action) {
case KMyMoneyRegister::ActionDeposit:
categoryLabel->setText(i18n("Category"));
cashflow->setDirection(KMyMoneyRegister::Deposit);
break;
case KMyMoneyRegister::ActionTransfer:
categoryLabel->setText(i18n("Transfer from"));
slotUpdateCashFlow(cashflow->direction());
break;
case KMyMoneyRegister::ActionWithdrawal:
categoryLabel->setText(i18n("Category"));
cashflow->setDirection(KMyMoneyRegister::Payment);
break;
}
}
}
void StdTransactionEditor::slotUpdateCashFlow(KMyMoneyRegister::CashFlowDirection dir)
{
TQLabel* categoryLabel = dynamic_cast<TQLabel*>(haveWidget("category-label"));
// tqDebug("Update cashflow to %d", dir);
if(categoryLabel) {
TabBar* tabbar = dynamic_cast<TabBar*>(haveWidget("tabbar"));
if(categoryLabel->text() != i18n("Category")) {
tabbar->setCurrentTab(KMyMoneyRegister::ActionTransfer);
if(dir == KMyMoneyRegister::Deposit) {
categoryLabel->setText(i18n("Transfer from"));
} else {
categoryLabel->setText(i18n("Transfer to"));
}
} else {
if(dir == KMyMoneyRegister::Deposit)
tabbar->setCurrentTab(KMyMoneyRegister::ActionDeposit);
else
tabbar->setCurrentTab(KMyMoneyRegister::ActionWithdrawal);
}
}
}
void StdTransactionEditor::slotUpdateCategory(const TQString& id)
{
TQLabel *categoryLabel = dynamic_cast<TQLabel*>(haveWidget("category-label"));
// tqDebug("Update category to %s", id.data());
if(categoryLabel) {
TabBar* tabbar = dynamic_cast<TabBar*>(haveWidget("tabbar"));
kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(m_editWidgets["amount"]);
MyMoneyMoney val = amount->value();
if(categoryLabel->text() == i18n("Transfer from")) {
val = -val;
} else {
val = val.abs();
}
if(tabbar) {
tabbar->tab(KMyMoneyRegister::ActionTransfer)->setEnabled(true);
tabbar->tab(KMyMoneyRegister::ActionDeposit)->setEnabled(true);
tabbar->tab(KMyMoneyRegister::ActionWithdrawal)->setEnabled(true);
}
if(!id.isEmpty()) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(id);
if(acc.isAssetLiability()
|| acc.accountGroup() == MyMoneyAccount::Equity) {
if(tabbar) {
tabbar->tab(KMyMoneyRegister::ActionDeposit)->setEnabled(false);
tabbar->tab(KMyMoneyRegister::ActionWithdrawal)->setEnabled(false);
}
// only change the label if an amount is already filled in
if(!val.isZero()) {
if(val.isNegative())
categoryLabel->setText(i18n("Transfer from"));
else
categoryLabel->setText(i18n("Transfer to"));
}
} else {
if(tabbar)
tabbar->tab(KMyMoneyRegister::ActionTransfer)->setEnabled(false);
categoryLabel->setText(i18n("Category"));
}
updateAmount(val.abs());
} else {
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
if(!category->currentText().isEmpty() && tabbar)
tabbar->tab(KMyMoneyRegister::ActionTransfer)->setEnabled(false);
categoryLabel->setText(i18n("Category"));
}
if(tabbar)
tabbar->update();
}
updateVAT(false);
}
void StdTransactionEditor::slotUpdatePayment(const TQString& txt)
{
MyMoneyMoney val(txt);
if(val.isNegative()) {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->setValue(val.abs());
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->clearText();
} else {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->clearText();
}
updateVAT();
}
void StdTransactionEditor::slotUpdateDeposit(const TQString& txt)
{
MyMoneyMoney val(txt);
if(val.isNegative()) {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->setValue(val.abs());
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"])->clearText();
} else {
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"])->clearText();
}
updateVAT();
}
void StdTransactionEditor::slotUpdateAmount(const TQString& txt)
{
// tqDebug("Update amount to %s", txt.data());
MyMoneyMoney val(txt);
updateAmount(val);
updateVAT(true);
}
void StdTransactionEditor::updateAmount(const MyMoneyMoney& val)
{
TQLabel *categoryLabel = dynamic_cast<TQLabel*>(haveWidget("category-label"));
if(categoryLabel) {
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(m_editWidgets["cashflow"]);
if(val.isNegative()) {
if(categoryLabel->text() != i18n("Category")) {
if(categoryLabel->text() == i18n("Transfer from")) {
categoryLabel->setText(i18n("Transfer to"));
cashflow->setDirection(KMyMoneyRegister::Payment);
} else {
categoryLabel->setText(i18n("Transfer from"));
cashflow->setDirection(KMyMoneyRegister::Deposit);
}
} else {
if(cashflow->direction() == KMyMoneyRegister::Deposit)
cashflow->setDirection(KMyMoneyRegister::Payment);
else
cashflow->setDirection(KMyMoneyRegister::Deposit);
slotUpdateCashFlow(cashflow->direction());
}
dynamic_cast<kMyMoneyEdit*>(m_editWidgets["amount"])->setValue(val.abs());
}
}
}
void StdTransactionEditor::updateVAT(bool amountChanged)
{
// make sure that we don't do this recursively
if(m_inUpdateVat)
return;
// we don't do anything if we have multiple transactions selected
if(isMultiSelection())
return;
// if auto vat assignment for this account is turned off
// we don't care about taxes
if(m_account.value("NoVat") == "Yes")
return;
// more splits than category and tax are not supported
if(m_splits.count() > 2)
return;
// in order to do anything, we need an amount
MyMoneyMoney amount, newAmount;
bool amountOk;
amount = amountFromWidget(&amountOk);
if(!amountOk)
return;
// If the transaction has a tax and a category split, remove the tax split
if(m_splits.count() == 2) {
newAmount = removeVatSplit();
if(m_splits.count() == 2) // not removed?
return;
} else {
// otherwise, we need a category
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
if(category->selectedItem().isEmpty())
return;
// if no VAT account is associated with this category/account, then we bail out
MyMoneyAccount cat = MyMoneyFile::instance()->account(category->selectedItem());
if(cat.value("VatAccount").isEmpty())
return;
newAmount = amount;
}
// seems we have everything we need
if(amountChanged)
newAmount = amount;
MyMoneyTransaction transaction;
if(createTransaction(transaction, m_transaction, m_split)) {
if(addVatSplit(transaction, newAmount)) {
m_transaction = transaction;
m_split = m_transaction.splits()[0];
loadEditWidgets();
// if we made this a split transaction, then move the
// focus to the memo field
if(tqApp->focusWidget() == haveWidget("category")) {
TQWidget* w = haveWidget("memo");
if(w)
w->setFocus();
}
}
}
}
bool StdTransactionEditor::addVatSplit(MyMoneyTransaction& tr, const MyMoneyMoney& amount)
{
if(tr.splitCount() != 2)
return false;
bool rc = false;
MyMoneyFile* file = MyMoneyFile::instance();
try {
MyMoneySplit cat; // category
MyMoneySplit tax; // tax
// extract the category split from the transaction
MyMoneyAccount category = file->account(tr.splitByAccount(m_account.id(), false).accountId());
if(category.value("VatAccount").isEmpty())
return false;
MyMoneyAccount vatAcc = file->account(category.value("VatAccount").latin1());
const MyMoneySecurity& asec = file->security(m_account.currencyId());
const MyMoneySecurity& csec = file->security(category.currencyId());
const MyMoneySecurity& vsec = file->security(vatAcc.currencyId());
if(asec.id() != csec.id() || asec.id() != vsec.id()) {
tqDebug("Auto VAT assignment only works if all three accounts use the same currency.");
return false;
}
MyMoneyMoney vatRate(vatAcc.value("VatRate"));
MyMoneyMoney gv, nv; // gross value, net value
int fract = m_account.fraction();
if(!vatRate.isZero()) {
tax.setAccountId(vatAcc.id());
// tqDebug("vat amount is '%s'", category.value("VatAmount").latin1());
if(category.value("VatAmount").lower() != TQString("net")) {
// split value is the gross value
gv = amount;
nv = gv / (MyMoneyMoney(1,1) + vatRate);
MyMoneySplit catSplit = tr.splitByAccount(m_account.id(), false);
catSplit.setShares(-nv.convert(fract));
catSplit.setValue(catSplit.shares());
tr.modifySplit(catSplit);
} else {
// split value is the net value
nv = amount;
gv = nv * (MyMoneyMoney(1,1) + vatRate);
MyMoneySplit accSplit = tr.splitByAccount(m_account.id());
accSplit.setValue(gv.convert(fract));
accSplit.setShares(accSplit.value());
tr.modifySplit(accSplit);
}
tax.setValue(-(gv - nv).convert(fract));
tax.setShares(tax.value());
tr.addSplit(tax);
rc = true;
}
} catch(MyMoneyException *e) {
delete e;
}
return rc;
}
MyMoneyMoney StdTransactionEditor::removeVatSplit(void)
{
// we only deal with splits that have three splits
if(m_splits.count() != 2)
return amountFromWidget();
MyMoneySplit c; // category split
MyMoneySplit t; // tax split
bool netValue = false;
TQValueList<MyMoneySplit>::const_iterator it_s;
for(it_s = m_splits.begin(); it_s != m_splits.end(); ++it_s) {
MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
if(!acc.value("VatAccount").isEmpty()) {
netValue = (acc.value("VatAmount").lower() == "net");
c = (*it_s);
} else if(!acc.value("VatRate").isEmpty()) {
t = (*it_s);
}
}
// bail out if not all splits are setup
if(c.id().isEmpty() || t.id().isEmpty())
return amountFromWidget();
MyMoneyMoney amount;
// reduce the splits
if(netValue) {
amount = -c.shares();
} else {
amount = -(c.shares() + t.shares());
}
// remove tax split from the list, ...
m_splits.clear();
m_splits.append(c);
// ... make sure that the widget is updated ...
// block the signals to avoid popping up the split editor dialog
// for nothing
m_editWidgets["category"]->blockSignals(true);
TQString id;
setupCategoryWidget(id);
m_editWidgets["category"]->blockSignals(false);
// ... and return the updated amount
return amount;
}
bool StdTransactionEditor::isComplete(TQString& reason) const
{
reason = TQString();
// reason.clear(); // for TQt4
TQMap<TQString, TQWidget*>::const_iterator it_w;
kMyMoneyDateInput* postDate = dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"]);
if(postDate) {
postDate->markAsBadDate();
TQToolTip::remove(postDate);
if(postDate->date().isValid() && (postDate->date() < m_account.openingDate())) {
postDate->markAsBadDate(true, KMyMoneyGlobalSettings::listNegativeValueColor());
reason = i18n("Cannot enter transaction with postdate prior to account's opening date.");
TQToolTip::add(postDate, reason);
return false;
}
}
for(it_w = m_editWidgets.begin(); it_w != m_editWidgets.end(); ++it_w) {
KMyMoneyPayeeCombo* payee = dynamic_cast<KMyMoneyPayeeCombo*>(*it_w);
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(*it_w);
kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(*it_w);
KMyMoneyReconcileCombo* reconcile = dynamic_cast<KMyMoneyReconcileCombo*>(*it_w);
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(*it_w);
if(payee && !(payee->currentText().isEmpty()))
break;
if(category && !category->lineEdit()->text().isEmpty())
break;
if(amount && !(amount->value().isZero()))
break;
// the following two widgets are only checked if we are editing multiple transactions
if(isMultiSelection()) {
if(reconcile && reconcile->state() != MyMoneySplit::Unknown)
break;
if(cashflow && cashflow->direction() != KMyMoneyRegister::Unknown)
break;
}
}
return it_w != m_editWidgets.end();
}
void StdTransactionEditor::slotCreateCategory(const TQString& name, TQString& id)
{
MyMoneyAccount acc, parent;
acc.setName(name);
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"));
if(cashflow) {
// form based input
if(cashflow->direction() == KMyMoneyRegister::Deposit)
parent = MyMoneyFile::instance()->income();
else
parent = MyMoneyFile::instance()->expense();
} else if(haveWidget("deposit")) {
// register based input
kMyMoneyEdit* deposit = dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"]);
if(deposit->value().isPositive())
parent = MyMoneyFile::instance()->income();
else
parent = MyMoneyFile::instance()->expense();
} else
parent = MyMoneyFile::instance()->expense();
// TODO extract possible first part of a hierarchy and check if it is one
// of our top categories. If so, remove it and select the parent
// according to this information.
emit createCategory(acc, parent);
// return id
id = acc.id();
}
int StdTransactionEditor::slotEditSplits(void)
{
int rc = TQDialog::Rejected;
if(!m_openEditSplits) {
// only get in here in a single instance
m_openEditSplits = true;
// force focus change to update all data
TQWidget* w = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"])->splitButton();
if(w)
w->setFocus();
kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(haveWidget("amount"));
kMyMoneyEdit* deposit = dynamic_cast<kMyMoneyEdit*>(haveWidget("deposit"));
kMyMoneyEdit* payment = dynamic_cast<kMyMoneyEdit*>(haveWidget("payment"));
KMyMoneyCashFlowCombo* cashflow = 0;
KMyMoneyRegister::CashFlowDirection dir = KMyMoneyRegister::Unknown;
bool isValidAmount = false;
if(amount) {
isValidAmount = amount->lineedit()->text().length() != 0;
cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"));
if(cashflow)
dir = cashflow->direction();
} else {
if(deposit) {
if (deposit->lineedit()->text().length() != 0) {
isValidAmount = true;
dir = KMyMoneyRegister::Deposit;
}
}
if(payment) {
if (payment->lineedit()->text().length() != 0) {
isValidAmount = true;
dir = KMyMoneyRegister::Payment;
}
}
if(!deposit || !payment) {
tqDebug("Internal error: deposit(%p) & payment(%p) widgets not found but required", deposit, payment);
return rc;
}
}
if(dir == KMyMoneyRegister::Unknown)
dir = KMyMoneyRegister::Payment;
MyMoneyTransaction transaction;
if(createTransaction(transaction, m_transaction, m_split)) {
MyMoneyMoney value;
KSplitTransactionDlg* dlg = new KSplitTransactionDlg(transaction,
transaction.splits()[0],
m_account,
isValidAmount,
dir == KMyMoneyRegister::Deposit,
0,
m_priceInfo,
m_regForm);
connect(dlg, TQT_SIGNAL(objectCreation(bool)), this, TQT_SIGNAL(objectCreation(bool)));
connect(dlg, TQT_SIGNAL(createCategory(MyMoneyAccount&, const MyMoneyAccount&)), this, TQT_SIGNAL(createCategory(MyMoneyAccount&, const MyMoneyAccount&)));
if((rc = dlg->exec()) == TQDialog::Accepted) {
m_transaction = dlg->transaction();
m_split = m_transaction.splits()[0];
loadEditWidgets();
}
delete dlg;
}
// focus jumps into the memo field
if((w = haveWidget("memo")) != 0) {
w->setFocus();
}
m_openEditSplits = false;
}
return rc;
}
void StdTransactionEditor::checkPayeeInSplit(MyMoneySplit& s, const TQString& payeeId)
{
if(s.accountId().isEmpty())
return;
MyMoneyAccount acc = MyMoneyFile::instance()->account(s.accountId());
if(acc.isIncomeExpense()) {
s.setPayeeId(payeeId);
} else {
if(s.payeeId().isEmpty())
s.setPayeeId(payeeId);
}
}
MyMoneyMoney StdTransactionEditor::amountFromWidget(bool* update) const
{
bool updateValue = false;
MyMoneyMoney value;
KMyMoneyCashFlowCombo* cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"));
if(cashflow) {
// form based input
kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(m_editWidgets["amount"]);
// if both fields do not contain changes -> no need to update
if(cashflow->direction() != KMyMoneyRegister::Unknown
&& !amount->lineedit()->text().isEmpty())
updateValue = true;
value = amount->value();
if(cashflow->direction() == KMyMoneyRegister::Payment)
value = -value;
} else if(haveWidget("deposit")) {
// register based input
kMyMoneyEdit* deposit = dynamic_cast<kMyMoneyEdit*>(m_editWidgets["deposit"]);
kMyMoneyEdit* payment = dynamic_cast<kMyMoneyEdit*>(m_editWidgets["payment"]);
// if both fields do not contain text -> no need to update
if(!(deposit->lineedit()->text().isEmpty() && payment->lineedit()->text().isEmpty()))
updateValue = true;
if(deposit->value().isPositive())
value = deposit->value();
else
value = -(payment->value());
}
if(update)
*update = updateValue;
// determine the max fraction for this account and
// adjust the value accordingly
return value.convert(m_account.fraction());
}
bool StdTransactionEditor::createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool skipPriceDialog)
{
// extract price info from original transaction
m_priceInfo.clear();
TQValueList<MyMoneySplit>::const_iterator it_s;
if(!torig.id().isEmpty()) {
for(it_s = torig.splits().begin(); it_s != torig.splits().end(); ++it_s) {
if((*it_s).id() != sorig.id()) {
MyMoneyAccount cat = MyMoneyFile::instance()->account((*it_s).accountId());
if(cat.currencyId() != m_account.currencyId()) {
if(!(*it_s).shares().isZero() && !(*it_s).value().isZero()) {
m_priceInfo[cat.currencyId()] = ((*it_s).shares() / (*it_s).value()).reduce();
}
}
}
}
}
t = torig;
t.removeSplits();
t.setCommodity(m_account.currencyId());
kMyMoneyDateInput* postDate = dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"]);
if(postDate->date().isValid()) {
t.setPostDate(postDate->date());
}
// we start with the previous values, make sure we can add them later on
MyMoneySplit s0 = sorig;
s0.clearId();
// make sure we reference this account here
s0.setAccountId(m_account.id());
// memo and number field are special: if we have multiple transactions selected
// and the edit field is empty, we treat it as "not modified".
// FIXME a better approach would be to have a 'dirty' flag with the widgets
// which identifies if the originally loaded value has been modified
// by the user
KTextEdit* memo = dynamic_cast<KTextEdit*>(m_editWidgets["memo"]);
if(memo) {
if(!isMultiSelection() || (isMultiSelection() && !memo->text().isEmpty() ) )
s0.setMemo(memo->text());
}
kMyMoneyLineEdit* number = dynamic_cast<kMyMoneyLineEdit*>(haveWidget("number"));
if(number) {
if(!isMultiSelection() || (isMultiSelection() && !number->text().isEmpty() ) )
s0.setNumber(number->text());
}
KMyMoneyPayeeCombo* payee = dynamic_cast<KMyMoneyPayeeCombo*>(m_editWidgets["payee"]);
TQString payeeId;
if(!isMultiSelection() || (isMultiSelection() && !payee->currentText().isEmpty())) {
payeeId = payee->selectedItem();
s0.setPayeeId(payeeId);
}
bool updateValue;
MyMoneyMoney value = amountFromWidget(&updateValue);
if(updateValue) {
// for this account, the shares and value is the same
s0.setValue(value);
s0.setShares(value);
} else {
value = s0.value();
}
// if we mark the split reconciled here, we'll use today's date if no reconciliation date is given
KMyMoneyReconcileCombo* status = dynamic_cast<KMyMoneyReconcileCombo*>(m_editWidgets["status"]);
if(status->state() != MyMoneySplit::Unknown)
s0.setReconcileFlag(status->state());
if(s0.reconcileFlag() == MyMoneySplit::Reconciled && !s0.reconcileDate().isValid())
s0.setReconcileDate(TQDate::currentDate());
checkPayeeInSplit(s0, payeeId);
// add the split to the transaction
t.addSplit(s0);
// if we have no other split we create it
// if we have none or only one other split, we reconstruct it here
// if we have more than one other split, we take them as they are
// make sure to perform all those changes on a local copy
TQValueList<MyMoneySplit> splits = m_splits;
MyMoneySplit s1;
if(splits.count() == 0) {
s1.setMemo(s0.memo());
splits.append(s1);
// make sure we will fill the value and share fields later on
updateValue = true;
}
// FIXME in multiSelection we currently only support transactions with one
// or two splits. So we check the original transaction and extract the other
// split or create it
if(isMultiSelection()) {
if(torig.splitCount() == 2) {
TQValueList<MyMoneySplit>::const_iterator it_s;
for(it_s = torig.splits().begin(); it_s != torig.splits().end(); ++it_s) {
if((*it_s).id() == sorig.id())
continue;
s1 = *it_s;
s1.clearId();
break;
}
}
} else {
if(splits.count() == 1) {
s1 = splits[0];
s1.clearId();
}
}
if(isMultiSelection() || splits.count() == 1) {
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
if(!isMultiSelection() || (isMultiSelection() && !category->currentText().isEmpty())) {
s1.setAccountId(category->selectedItem());
}
// if the first split has a memo but the second split is empty,
// we just copy the memo text over
if(memo) {
if(!isMultiSelection() || (isMultiSelection() && !memo->text().isEmpty())) {
// if the memo is filled, we check if the
// account referenced by s1 is a regular account or a category.
// in case of a regular account, we just leave the memo as is
// in case of a category we simply copy the new value over the old.
// in case we don't even have an account id, we just skip because
// the split will be removed later on anyway.
if(!s1.memo().isEmpty()) {
if(!s1.accountId().isEmpty()) {
MyMoneyAccount acc = MyMoneyFile::instance()->account(s1.accountId());
if(acc.isIncomeExpense()) {
s1.setMemo(s0.memo());
}
}
} else {
s1.setMemo(s0.memo());
}
}
}
if(updateValue && !s1.accountId().isEmpty()) {
s1.setValue(-value);
MyMoneyMoney shares;
if(!skipPriceDialog) {
if(!KCurrencyCalculator::setupSplitPrice(shares, t, s1, m_priceInfo, m_regForm))
return false;
} else {
MyMoneyAccount cat = MyMoneyFile::instance()->account(s1.accountId());
if(m_priceInfo.find(cat.currencyId()) != m_priceInfo.end()) {
shares = (s1.value() * m_priceInfo[cat.currencyId()]).reduce().convert(cat.fraction());
}
else
shares = s1.value();
}
s1.setShares(shares);
}
checkPayeeInSplit(s1, payeeId);
if(!s1.accountId().isEmpty())
t.addSplit(s1);
} else {
TQValueList<MyMoneySplit>::iterator it_s;
for(it_s = splits.begin(); it_s != splits.end(); ++it_s) {
s1 = *it_s;
s1.clearId();
checkPayeeInSplit(s1, payeeId);
t.addSplit(s1);
}
}
return true;
}
void StdTransactionEditor::setupFinalWidgets(void)
{
addFinalWidget(haveWidget("deposit"));
addFinalWidget(haveWidget("payment"));
addFinalWidget(haveWidget("amount"));
addFinalWidget(haveWidget("status"));
}
void StdTransactionEditor::slotUpdateAccount(const TQString& id)
{
TransactionEditor::slotUpdateAccount(id);
KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets["category"]);
if(category && category->splitButton()) {
category->splitButton()->setDisabled(id.isEmpty());
}
}
#include "transactioneditor.moc"