File indexing completed on 2024-05-12 16:42:12
0001 /* 0002 SPDX-FileCopyrightText: 2009-2018 Thomas Baumgart <tbaumgart@kde.org> 0003 SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "stdtransactioneditor.h" 0008 #include "transactioneditor_p.h" 0009 0010 // ---------------------------------------------------------------------------- 0011 // QT Includes 0012 0013 #include <QLabel> 0014 #include <QApplication> 0015 #include <QList> 0016 #include <QPushButton> 0017 0018 // ---------------------------------------------------------------------------- 0019 // KDE Includes 0020 0021 #include <KTextEdit> 0022 #include <KLocalizedString> 0023 #include <KMessageBox> 0024 #include <KStandardGuiItem> 0025 0026 // ---------------------------------------------------------------------------- 0027 // Project Includes 0028 0029 #include "kmymoneyreconcilecombo.h" 0030 #include "kmymoneycashflowcombo.h" 0031 #include "kmymoneypayeecombo.h" 0032 #include "kmymoneytagcombo.h" 0033 #include "ktagcontainer.h" 0034 #include "tabbar.h" 0035 #include "kmymoneycategory.h" 0036 #include "kmymoneymvccombo.h" 0037 #include "kmymoneydateinput.h" 0038 #include "amountedit.h" 0039 #include "kmymoneylineedit.h" 0040 #include "kmymoneyaccountselector.h" 0041 #include "mymoneyfile.h" 0042 #include "mymoneypayee.h" 0043 #include "mymoneytag.h" 0044 #include "kmymoneyutils.h" 0045 #include "kmymoneycompletion.h" 0046 #include "transaction.h" 0047 #include "transactionform.h" 0048 #include "mymoneytransactionfilter.h" 0049 #include "kmymoneysettings.h" 0050 #include "transactioneditorcontainer.h" 0051 0052 #include "ksplittransactiondlg.h" 0053 #include "kcurrencycalculator.h" 0054 #include "kselecttransactionsdlg.h" 0055 #include "widgetenums.h" 0056 0057 using namespace eWidgets; 0058 using namespace KMyMoneyRegister; 0059 using namespace KMyMoneyTransactionForm; 0060 0061 class StdTransactionEditorPrivate : public TransactionEditorPrivate 0062 { 0063 Q_DISABLE_COPY(StdTransactionEditorPrivate) 0064 0065 public: 0066 explicit StdTransactionEditorPrivate(StdTransactionEditor *qq) : 0067 TransactionEditorPrivate(qq), 0068 m_inUpdateVat(false) 0069 { 0070 } 0071 0072 ~StdTransactionEditorPrivate() 0073 { 0074 } 0075 0076 MyMoneyMoney m_shares; 0077 bool m_inUpdateVat; 0078 }; 0079 0080 StdTransactionEditor::StdTransactionEditor() : 0081 TransactionEditor(*new StdTransactionEditorPrivate(this)) 0082 { 0083 } 0084 0085 StdTransactionEditor::StdTransactionEditor(TransactionEditorContainer* regForm, 0086 KMyMoneyRegister::Transaction* item, 0087 const KMyMoneyRegister::SelectedTransactions& list, 0088 const QDate& lastPostDate) : 0089 TransactionEditor(*new StdTransactionEditorPrivate(this), 0090 regForm, 0091 item, 0092 list, 0093 lastPostDate) 0094 { 0095 } 0096 0097 StdTransactionEditor::~StdTransactionEditor() 0098 { 0099 } 0100 0101 void StdTransactionEditor::createEditWidgets() 0102 { 0103 Q_D(StdTransactionEditor); 0104 // we only create the account widget in case it is needed 0105 // to avoid confusion in the tab order later on. 0106 if (d->m_item->showRowInForm(0)) { 0107 auto account = new KMyMoneyCategory; 0108 account->setPlaceholderText(i18n("Account")); 0109 account->setObjectName(QLatin1String("Account")); 0110 d->m_editWidgets["account"] = account; 0111 connect(account, &QComboBox::editTextChanged, this, &StdTransactionEditor::slotUpdateButtonState); 0112 connect(account, &KMyMoneyCombo::itemSelected, this, &StdTransactionEditor::slotUpdateAccount); 0113 } 0114 0115 auto payee = new KMyMoneyPayeeCombo; 0116 payee->setPlaceholderText(i18n("Payer/Receiver")); 0117 payee->setObjectName(QLatin1String("Payee")); 0118 d->m_editWidgets["payee"] = payee; 0119 0120 connect(payee, &KMyMoneyMVCCombo::createItem, this, &StdTransactionEditor::slotNewPayee); 0121 connect(payee, &KMyMoneyMVCCombo::objectCreation, this, &StdTransactionEditor::objectCreation); 0122 connect(payee, &KMyMoneyMVCCombo::itemSelected, this, &StdTransactionEditor::slotUpdatePayee); 0123 connect(payee, &QComboBox::editTextChanged, this, &StdTransactionEditor::slotUpdateButtonState); 0124 0125 auto category = new KMyMoneyCategory(true, nullptr); 0126 category->setPlaceholderText(i18n("Category/Account")); 0127 category->setObjectName(QLatin1String("Category/Account")); 0128 d->m_editWidgets["category"] = category; 0129 connect(category, &KMyMoneyCombo::itemSelected, this, &StdTransactionEditor::slotUpdateCategory); 0130 connect(category, &QComboBox::editTextChanged, this, &StdTransactionEditor::slotUpdateButtonState); 0131 connect(category, &KMyMoneyCombo::createItem, this, &StdTransactionEditor::slotCreateCategory); 0132 connect(category, &KMyMoneyCombo::objectCreation, this, &StdTransactionEditor::objectCreation); 0133 connect(category->splitButton(), &QAbstractButton::clicked, this, &StdTransactionEditor::slotEditSplits); 0134 // initially disable the split button since we don't have an account set 0135 if (category->splitButton()) 0136 category->splitButton()->setDisabled(d->m_account.id().isEmpty()); 0137 0138 auto tag = new KTagContainer; 0139 tag->tagCombo()->setPlaceholderText(i18n("Tag")); 0140 tag->tagCombo()->setObjectName(QLatin1String("Tag")); 0141 d->m_editWidgets["tag"] = tag; 0142 connect(tag->tagCombo(), &KMyMoneyMVCCombo::createItem, this, &StdTransactionEditor::slotNewTag); 0143 connect(tag->tagCombo(), &KMyMoneyMVCCombo::objectCreation, this, &StdTransactionEditor::objectCreation); 0144 0145 auto memo = new KTextEdit; 0146 memo->setObjectName(QLatin1String("Memo")); 0147 memo->setTabChangesFocus(true); 0148 connect(memo, &QTextEdit::textChanged, this, &StdTransactionEditor::slotUpdateMemoState); 0149 connect(memo, &QTextEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState); 0150 d->m_editWidgets["memo"] = memo; 0151 d->m_memoText.clear(); 0152 d->m_memoChanged = false; 0153 0154 bool showNumberField = true; 0155 switch (d->m_account.accountType()) { 0156 case eMyMoney::Account::Type::Savings: 0157 case eMyMoney::Account::Type::Cash: 0158 case eMyMoney::Account::Type::Loan: 0159 case eMyMoney::Account::Type::AssetLoan: 0160 case eMyMoney::Account::Type::Asset: 0161 case eMyMoney::Account::Type::Liability: 0162 case eMyMoney::Account::Type::Equity: 0163 showNumberField = KMyMoneySettings::alwaysShowNrField(); 0164 break; 0165 0166 case eMyMoney::Account::Type::Income: 0167 case eMyMoney::Account::Type::Expense: 0168 showNumberField = false; 0169 break; 0170 0171 default: 0172 break; 0173 } 0174 0175 if (showNumberField) { 0176 auto number = new KMyMoneyLineEdit; 0177 number->setPlaceholderText(i18n("Number")); 0178 number->setObjectName(QLatin1String("Number")); 0179 d->m_editWidgets["number"] = number; 0180 connect(number, &KMyMoneyLineEdit::lineChanged, this, &StdTransactionEditor::slotNumberChanged); 0181 // number->installEventFilter(this); 0182 } 0183 0184 auto postDate = new KMyMoneyDateInput; 0185 d->m_editWidgets["postdate"] = postDate; 0186 postDate->setObjectName(QLatin1String("PostDate")); 0187 connect(postDate, &KMyMoneyDateInput::dateChanged, this, &StdTransactionEditor::slotUpdateButtonState); 0188 postDate->setDate(QDate()); 0189 0190 auto value = new AmountEdit; 0191 d->m_editWidgets["amount"] = value; 0192 value->setObjectName(QLatin1String("Amount")); 0193 value->setCalculatorButtonVisible(true); 0194 connect(value, &AmountEdit::valueChanged, this, &StdTransactionEditor::slotUpdateAmount); 0195 connect(value, &AmountEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState); 0196 0197 value = new AmountEdit; 0198 d->m_editWidgets["payment"] = value; 0199 value->setObjectName(QLatin1String("Payment")); 0200 value->setCalculatorButtonVisible(true); 0201 connect(value, &AmountEdit::valueChanged, this, &StdTransactionEditor::slotUpdatePayment); 0202 connect(value, &AmountEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState); 0203 0204 value = new AmountEdit; 0205 d->m_editWidgets["deposit"] = value; 0206 value->setObjectName(QLatin1String("Deposit")); 0207 value->setCalculatorButtonVisible(true); 0208 connect(value, &AmountEdit::valueChanged, this, &StdTransactionEditor::slotUpdateDeposit); 0209 connect(value, &AmountEdit::textChanged, this, &StdTransactionEditor::slotUpdateButtonState); 0210 0211 auto cashflow = new KMyMoneyCashFlowCombo(d->m_account.accountGroup(), nullptr); 0212 d->m_editWidgets["cashflow"] = cashflow; 0213 cashflow->setObjectName(QLatin1String("Cashflow")); 0214 connect(cashflow, &KMyMoneyCashFlowCombo::directionSelected, this, &StdTransactionEditor::slotUpdateCashFlow); 0215 0216 auto reconcile = new KMyMoneyReconcileCombo; 0217 d->m_editWidgets["status"] = reconcile; 0218 reconcile->setObjectName(QLatin1String("Reconcile")); 0219 0220 KMyMoneyRegister::QWidgetContainer::iterator it_w; 0221 for (it_w = d->m_editWidgets.begin(); it_w != d->m_editWidgets.end(); ++it_w) { 0222 (*it_w)->installEventFilter(this); 0223 } 0224 // if we don't have more than 1 selected transaction, we don't need 0225 // the "don't change" item in some of the combo widgets 0226 if (!isMultiSelection()) { 0227 reconcile->removeDontCare(); 0228 cashflow->removeDontCare(); 0229 } 0230 0231 QLabel* label; 0232 d->m_editWidgets["account-label"] = label = new QLabel(i18n("Account")); 0233 label->setAlignment(Qt::AlignVCenter); 0234 0235 d->m_editWidgets["category-label"] = label = new QLabel(i18n("Category")); 0236 label->setAlignment(Qt::AlignVCenter); 0237 0238 d->m_editWidgets["tag-label"] = label = new QLabel(i18n("Tags")); 0239 label->setAlignment(Qt::AlignVCenter); 0240 0241 d->m_editWidgets["memo-label"] = label = new QLabel(i18n("Memo")); 0242 label->setAlignment(Qt::AlignVCenter); 0243 0244 d->m_editWidgets["number-label"] = label = new QLabel(i18n("Number")); 0245 label->setAlignment(Qt::AlignVCenter); 0246 0247 d->m_editWidgets["date-label"] = label = new QLabel(i18n("Date")); 0248 label->setAlignment(Qt::AlignVCenter); 0249 0250 d->m_editWidgets["amount-label"] = label = new QLabel(i18n("Amount")); 0251 label->setAlignment(Qt::AlignVCenter); 0252 0253 d->m_editWidgets["status-label"] = label = new QLabel(i18n("Status")); 0254 label->setAlignment(Qt::AlignVCenter); 0255 0256 // create a copy of tabbar above the form (if we are created for a form) 0257 auto form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(d->m_regForm); 0258 if (form) { 0259 auto tabbar = new KMyMoneyTransactionForm::TabBar; 0260 d->m_editWidgets["tabbar"] = tabbar; 0261 tabbar->setObjectName(QLatin1String("TabBar")); 0262 tabbar->copyTabs(form->getTabBar()); 0263 connect(tabbar, &KMyMoneyTransactionForm::TabBar::tabCurrentChanged, this, &StdTransactionEditor::slotUpdateAction); 0264 connect(tabbar, &KMyMoneyTransactionForm::TabBar::tabCurrentChanged, this, &TransactionEditor::operationTypeChanged); 0265 } 0266 0267 setupPrecision(); 0268 } 0269 0270 void StdTransactionEditor::setupCategoryWidget(QString& categoryId) 0271 { 0272 Q_D(StdTransactionEditor); 0273 if (auto categoryWidget = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) 0274 TransactionEditor::setupCategoryWidget(categoryWidget, d->m_splits, categoryId, SLOT(slotEditSplits())); 0275 0276 if (d->m_splits.count() == 1) 0277 d->m_shares = d->m_splits[0].shares(); 0278 } 0279 0280 bool StdTransactionEditor::isTransfer(const QString& accId1, const QString& accId2) const 0281 { 0282 if (accId1.isEmpty() || accId2.isEmpty()) 0283 return false; 0284 0285 return MyMoneyFile::instance()->account(accId1).isIncomeExpense() == MyMoneyFile::instance()->account(accId2).isIncomeExpense(); 0286 } 0287 0288 void StdTransactionEditor::loadEditWidgets(eRegister::Action action) 0289 { 0290 Q_D(StdTransactionEditor); 0291 // don't kick off VAT processing from here 0292 d->m_inUpdateVat = true; 0293 0294 QWidget* w; 0295 AccountSet aSet; 0296 0297 // load the account widget 0298 if (auto account = dynamic_cast<KMyMoneyCategory*>(haveWidget("account"))) { 0299 aSet.addAccountGroup(eMyMoney::Account::Type::Asset); 0300 aSet.addAccountGroup(eMyMoney::Account::Type::Liability); 0301 aSet.removeAccountType(eMyMoney::Account::Type::AssetLoan); 0302 aSet.removeAccountType(eMyMoney::Account::Type::CertificateDep); 0303 aSet.removeAccountType(eMyMoney::Account::Type::Investment); 0304 aSet.removeAccountType(eMyMoney::Account::Type::Stock); 0305 aSet.removeAccountType(eMyMoney::Account::Type::MoneyMarket); 0306 aSet.removeAccountType(eMyMoney::Account::Type::Loan); 0307 aSet.load(account->selector()); 0308 account->completion()->setSelected(d->m_account.id()); 0309 account->slotItemSelected(d->m_account.id()); 0310 } 0311 0312 // load the payee widget 0313 auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_editWidgets["payee"]); 0314 if (payee) 0315 payee->loadPayees(MyMoneyFile::instance()->payeeList()); 0316 0317 // load the category widget 0318 auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]); 0319 0320 if (category) 0321 disconnect(category, &KMyMoneyCategory::focusIn, this, &StdTransactionEditor::slotEditSplits); 0322 0323 // load the tag widget 0324 //auto tag = dynamic_cast<KMyMoneyTagCombo*>(m_editWidgets["tag"]); 0325 auto tag = dynamic_cast<KTagContainer*>(d->m_editWidgets["tag"]); 0326 if (tag) 0327 tag->loadTags(MyMoneyFile::instance()->tagList()); 0328 0329 // check if the current transaction has a reference to an equity account 0330 auto haveEquityAccount = false; 0331 foreach (const auto split, d->m_transaction.splits()) { 0332 auto acc = MyMoneyFile::instance()->account(split.accountId()); 0333 if (acc.accountType() == eMyMoney::Account::Type::Equity) { 0334 haveEquityAccount = true; 0335 break; 0336 } 0337 } 0338 0339 aSet.clear(); 0340 aSet.addAccountGroup(eMyMoney::Account::Type::Asset); 0341 aSet.addAccountGroup(eMyMoney::Account::Type::Liability); 0342 aSet.addAccountGroup(eMyMoney::Account::Type::Income); 0343 aSet.addAccountGroup(eMyMoney::Account::Type::Expense); 0344 if (KMyMoneySettings::expertMode() || haveEquityAccount) 0345 aSet.addAccountGroup(eMyMoney::Account::Type::Equity); 0346 0347 aSet.removeAccountType(eMyMoney::Account::Type::CertificateDep); 0348 aSet.removeAccountType(eMyMoney::Account::Type::Investment); 0349 aSet.removeAccountType(eMyMoney::Account::Type::Stock); 0350 aSet.removeAccountType(eMyMoney::Account::Type::MoneyMarket); 0351 if (category) 0352 aSet.load(category->selector()); 0353 0354 // if an account is specified then remove it from the widget so that the user 0355 // cannot create a transfer with from and to account being the same account 0356 if (category && !d->m_account.id().isEmpty()) 0357 category->selector()->removeItem(d->m_account.id()); 0358 0359 // also show memo text if isMultiSelection() 0360 if (auto memoWidget = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"])) 0361 memoWidget->setText(d->m_split.memo()); 0362 // need to know if it changed 0363 d->m_memoText = d->m_split.memo(); 0364 d->m_memoChanged = false; 0365 0366 if (!isMultiSelection()) { 0367 if (auto dateWidget = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"])) { 0368 if (d->m_transaction.postDate().isValid()) 0369 dateWidget->setDate(d->m_transaction.postDate()); 0370 else if (d->m_lastPostDate.isValid()) 0371 dateWidget->setDate(d->m_lastPostDate); 0372 else 0373 dateWidget->setDate(QDate::currentDate()); 0374 } 0375 0376 if ((w = haveWidget("number")) != 0) { 0377 if (auto lineEdit = dynamic_cast<KMyMoneyLineEdit*>(w)) 0378 lineEdit->loadText(d->m_split.number()); 0379 if (d->m_transaction.id().isEmpty() // new transaction 0380 && dynamic_cast<KMyMoneyLineEdit*>(w)->text().isEmpty() // no number filled in 0381 && d->m_account.accountType() == eMyMoney::Account::Type::Checkings // checkings account 0382 && KMyMoneySettings::autoIncCheckNumber() // and auto inc number turned on? 0383 && action != eRegister::Action::Deposit // only transfers or withdrawals 0384 && d->m_paymentMethod == eMyMoney::Schedule::PaymentType::WriteChecque) {// only for WriteChecque 0385 assignNextNumber(); 0386 } 0387 } 0388 if (auto statusWidget = dynamic_cast<KMyMoneyReconcileCombo*>(d->m_editWidgets["status"])) 0389 statusWidget->setState(d->m_split.reconcileFlag()); 0390 0391 QString payeeId = d->m_split.payeeId(); 0392 if (payee && !payeeId.isEmpty()) 0393 payee->setSelectedItem(payeeId); 0394 0395 QList<QString> t = d->m_split.tagIdList(); 0396 if (tag && !t.isEmpty()) 0397 for (auto i = 0; i < t.size(); ++i) 0398 tag->addTagWidget(t[i]); 0399 0400 d->m_splits.clear(); 0401 if (d->m_transaction.splitCount() < 2) { 0402 if (category && category->completion()) { 0403 category->completion()->setSelected(QString()); 0404 } 0405 } else { 0406 foreach (const auto split, d->m_transaction.splits()) { 0407 if (split == d->m_split) 0408 continue; 0409 d->m_splits.append(split); 0410 } 0411 } 0412 QString categoryId; 0413 setupCategoryWidget(categoryId); 0414 0415 if ((w = haveWidget("cashflow")) != 0) { 0416 if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(w)) 0417 cashflow->setDirection(!d->m_split.value().isPositive() ? eRegister::CashFlowDirection::Payment : eRegister::CashFlowDirection::Deposit); // include isZero case 0418 } 0419 0420 if ((w = haveWidget("category-label")) != 0) { 0421 if (auto categoryLabel = dynamic_cast<QLabel*>(w)) { 0422 if (isTransfer(d->m_split.accountId(), categoryId)) { 0423 if (d->m_split.value().isPositive()) 0424 categoryLabel->setText(i18n("Transfer from")); 0425 else 0426 categoryLabel->setText(i18n("Transfer to")); 0427 } 0428 } 0429 } 0430 0431 MyMoneyMoney value = d->m_split.shares(); 0432 0433 if (haveWidget("deposit")) { 0434 auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]); 0435 auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]); 0436 if (depositWidget && paymentWidget) { 0437 if (d->m_split.shares().isNegative()) { 0438 depositWidget->setText(QString()); 0439 paymentWidget->setValue(value.abs()); 0440 } else { 0441 depositWidget->setValue(value.abs()); 0442 paymentWidget->setText(QString()); 0443 } 0444 } 0445 } 0446 if ((w = haveWidget("amount")) != 0) { 0447 if (auto amountWidget = dynamic_cast<AmountEdit*>(w)) 0448 amountWidget->setValue(value.abs()); 0449 } 0450 0451 // try to preset for specific action if a new transaction is being started 0452 if (d->m_transaction.id().isEmpty()) { 0453 if ((w = haveWidget("category-label")) != 0) { 0454 auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar")); 0455 if (action == eRegister::Action::None) { 0456 if (tabbar) { 0457 action = static_cast<eRegister::Action>(tabbar->currentIndex()); 0458 } 0459 } 0460 if (action != eRegister::Action::None) { 0461 if (auto categoryLabel = dynamic_cast<QLabel*>(w)) { 0462 if (action == eRegister::Action::Transfer) { 0463 if (d->m_split.value().isPositive()) 0464 categoryLabel->setText(i18n("Transfer from")); 0465 else 0466 categoryLabel->setText(i18n("Transfer to")); 0467 } 0468 } 0469 if ((w = haveWidget("cashflow")) != 0) { 0470 if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(w)) { 0471 if (action == eRegister::Action::Deposit || (action == eRegister::Action::Transfer && d->m_split.value().isPositive())) 0472 cashflow->setDirection(eRegister::CashFlowDirection::Deposit); 0473 else 0474 cashflow->setDirection(eRegister::CashFlowDirection::Payment); 0475 } 0476 } 0477 if (tabbar) { 0478 tabbar->setCurrentIndex((int)action); 0479 } 0480 } 0481 } 0482 } else { 0483 if (auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"))) { 0484 if (!isTransfer(d->m_split.accountId(), categoryId)) 0485 tabbar->setCurrentIndex(d->m_split.value().isNegative() ? (int)eRegister::Action::Withdrawal : (int)eRegister::Action::Deposit); 0486 else 0487 tabbar->setCurrentIndex((int)eRegister::Action::Transfer); 0488 } 0489 } 0490 0491 } else { // isMultiSelection() 0492 if (auto postDateWidget = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"])) 0493 postDateWidget->loadDate(QDate()); 0494 if (auto statusWidget = dynamic_cast<KMyMoneyReconcileCombo*>(d->m_editWidgets["status"])) 0495 statusWidget->setState(eMyMoney::Split::State::Unknown); 0496 if (haveWidget("deposit")) { 0497 if (auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"])) { 0498 depositWidget->setText(QString()); 0499 depositWidget->setAllowEmpty(); 0500 } 0501 if (auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"])) { 0502 paymentWidget->setText(QString()); 0503 paymentWidget->setAllowEmpty(); 0504 } 0505 } 0506 if ((w = haveWidget("amount")) != 0) { 0507 if (auto amountWidget = dynamic_cast<AmountEdit*>(w)) { 0508 amountWidget->setText(QString()); 0509 amountWidget->setAllowEmpty(); 0510 } 0511 } 0512 0513 slotUpdateAction((int)action); 0514 0515 if ((w = haveWidget("tabbar")) != 0) { 0516 w->setEnabled(false); 0517 } 0518 0519 if (category && category->completion()) 0520 category->completion()->setSelected(QString()); 0521 } 0522 0523 // allow kick off VAT processing again 0524 d->m_inUpdateVat = false; 0525 } 0526 0527 void StdTransactionEditor::loadEditWidgets() 0528 { 0529 loadEditWidgets(eRegister::Action::None); 0530 } 0531 0532 QWidget* StdTransactionEditor::firstWidget() const 0533 { 0534 Q_D(const StdTransactionEditor); 0535 QWidget* w = nullptr; 0536 if (d->m_initialAction != eRegister::Action::None) { 0537 w = haveWidget("payee"); 0538 } 0539 return w; 0540 } 0541 0542 void StdTransactionEditor::slotReloadEditWidgets() 0543 { 0544 Q_D(StdTransactionEditor); 0545 // reload category widget 0546 if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) { 0547 QString categoryId = category->selectedItem(); 0548 0549 AccountSet aSet; 0550 aSet.addAccountGroup(eMyMoney::Account::Type::Asset); 0551 aSet.addAccountGroup(eMyMoney::Account::Type::Liability); 0552 aSet.addAccountGroup(eMyMoney::Account::Type::Income); 0553 aSet.addAccountGroup(eMyMoney::Account::Type::Expense); 0554 if (KMyMoneySettings::expertMode()) 0555 aSet.addAccountGroup(eMyMoney::Account::Type::Equity); 0556 aSet.load(category->selector()); 0557 0558 // if an account is specified then remove it from the widget so that the user 0559 // cannot create a transfer with from and to account being the same account 0560 if (!d->m_account.id().isEmpty()) 0561 category->selector()->removeItem(d->m_account.id()); 0562 0563 if (!categoryId.isEmpty()) 0564 category->setSelectedItem(categoryId); 0565 } 0566 0567 0568 // reload payee widget 0569 if (auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_editWidgets["payee"])) { 0570 QString payeeId = payee->selectedItem(); 0571 0572 payee->loadPayees(MyMoneyFile::instance()->payeeList()); 0573 0574 if (!payeeId.isEmpty()) { 0575 payee->setSelectedItem(payeeId); 0576 } 0577 } 0578 0579 // reload tag widget 0580 if (auto tag = dynamic_cast<KTagContainer*>(d->m_editWidgets["tag"])) { 0581 QString tagId = tag->tagCombo()->selectedItem(); 0582 0583 tag->loadTags(MyMoneyFile::instance()->tagList()); 0584 0585 if (!tagId.isEmpty()) { 0586 tag->RemoveAllTagWidgets(); 0587 tag->addTagWidget(tagId); 0588 } 0589 } 0590 } 0591 0592 void StdTransactionEditor::slotUpdatePayee(const QString& payeeId) 0593 { 0594 // in case of an empty payee, there is nothing to do 0595 if (payeeId.isEmpty()) 0596 return; 0597 0598 Q_D(StdTransactionEditor); 0599 // we have a new payee assigned to this transaction. 0600 // in case there is no category assigned, no value entered and no 0601 // memo available, we search for the last transaction of this payee 0602 // in the account. 0603 if (d->m_transaction.id().isEmpty() // 0604 && d->m_splits.count() == 0 // 0605 && KMyMoneySettings::autoFillTransaction() != 0) { 0606 // check if category is empty 0607 if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) { 0608 QStringList list; 0609 category->selectedItems(list); 0610 if (!list.isEmpty()) 0611 return; 0612 } 0613 0614 // check if memo is empty 0615 auto memo = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]); 0616 if (memo && !memo->toPlainText().isEmpty()) 0617 return; 0618 0619 // check if all value fields are empty 0620 QStringList fields{"amount", "payment", "deposit"}; 0621 QStringList::const_iterator it_f; 0622 for (it_f = fields.constBegin(); it_f != fields.constEnd(); ++it_f) { 0623 const auto amount = dynamic_cast<AmountEdit*>(haveWidget(*it_f)); 0624 if (amount && !amount->value().isZero()) 0625 return; 0626 } 0627 0628 #if 0 0629 // Tony mentioned, that autofill does not work when he changed the date. Well, 0630 // that certainly makes sense when you enter transactions in register mode as 0631 // opposed to form mode, because the date field is located prior to the date 0632 // field in the tab order of the widgets and the user might have already 0633 // changed it. 0634 // 0635 // So I commented out the code that checks the date but left it in for reference. 0636 // (ipwizard, 2008-04-07) 0637 0638 // check if date has been altered by user 0639 auto postDate = dynamic_cast<KMyMoneyDateInput*>(m_editWidgets["postdate"]); 0640 if (postDate && (m_lastPostDate.isValid() && (postDate->date() != m_lastPostDate)) 0641 || (!m_lastPostDate.isValid() && (postDate->date() != QDate::currentDate()))) 0642 return; 0643 #endif 0644 0645 // if we got here, we have to autofill 0646 autoFill(payeeId); 0647 } 0648 0649 // If payee has associated default account (category), set that now. 0650 const MyMoneyPayee& payeeObj = MyMoneyFile::instance()->payee(payeeId); 0651 if (!payeeObj.defaultAccountId().isEmpty()) { 0652 if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) 0653 category->slotItemSelected(payeeObj.defaultAccountId()); 0654 } 0655 } 0656 0657 MyMoneyMoney StdTransactionEditor::shares(const MyMoneyTransaction& t) const 0658 { 0659 Q_D(const StdTransactionEditor); 0660 MyMoneyMoney result; 0661 foreach (const auto split, t.splits()) { 0662 if (split.accountId() == d->m_account.id()) { 0663 result += split.shares(); 0664 } 0665 } 0666 return result; 0667 } 0668 0669 struct uniqTransaction { 0670 const MyMoneyTransaction* t; 0671 int cnt; 0672 }; 0673 0674 void StdTransactionEditor::autoFill(const QString& payeeId) 0675 { 0676 Q_D(StdTransactionEditor); 0677 QList<QPair<MyMoneyTransaction, MyMoneySplit> > list; 0678 MyMoneyTransactionFilter filter(d->m_account.id()); 0679 filter.addPayee(payeeId); 0680 MyMoneyFile::instance()->transactionList(list, filter); 0681 if (!list.empty()) { 0682 // ok, we found at least one previous transaction. now we clear out 0683 // what we have collected so far and add those splits from 0684 // the previous transaction. 0685 QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it_t; 0686 QMap<QString, struct uniqTransaction> uniqList; 0687 0688 // collect the transactions and see if we have any duplicates 0689 for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { 0690 QString key = (*it_t).first.accountSignature(); 0691 int cnt = 0; 0692 QMap<QString, struct uniqTransaction>::iterator it_u; 0693 do { 0694 QString ukey = QString("%1-%2").arg(key).arg(cnt); 0695 it_u = uniqList.find(ukey); 0696 if (it_u == uniqList.end()) { 0697 uniqList[ukey].t = &((*it_t).first); 0698 uniqList[ukey].cnt = 1; 0699 } else if (KMyMoneySettings::autoFillTransaction() == 1) { 0700 // we already have a transaction with this signature. we must 0701 // now check, if we should really treat it as a duplicate according 0702 // to the value comparison delta. 0703 MyMoneyMoney s1 = shares(*((*it_u).t)); 0704 MyMoneyMoney s2 = shares((*it_t).first); 0705 if (s2.abs() > s1.abs()) { 0706 MyMoneyMoney t(s1); 0707 s1 = s2; 0708 s2 = t; 0709 } 0710 MyMoneyMoney diff; 0711 if (s2.isZero()) 0712 diff = s1.abs(); 0713 else 0714 diff = ((s1 - s2) / s2).convert(10000); 0715 if (diff.isPositive() && diff <= MyMoneyMoney(KMyMoneySettings::autoFillDifference(), 100)) { 0716 uniqList[ukey].t = &((*it_t).first); 0717 break; // end while loop 0718 } 0719 } else if (KMyMoneySettings::autoFillTransaction() == 2) { 0720 uniqList[ukey].t = &((*it_t).first); 0721 (*it_u).cnt++; 0722 break; // end while loop 0723 } 0724 ++cnt; 0725 } while (it_u != uniqList.end()); 0726 0727 } 0728 0729 MyMoneyTransaction t; 0730 if (KMyMoneySettings::autoFillTransaction() != 2) { 0731 #if 0 0732 // I removed this code to allow cancellation of an autofill if 0733 // it does not match even if there is only a single matching 0734 // transaction for the payee in question. In case, we want to revert 0735 // to the old behavior, don't forget to uncomment the closing 0736 // brace further down in the code as well. (ipwizard 2009-01-16) 0737 if (uniqList.count() == 1) { 0738 t = list.last().first; 0739 } else { 0740 #endif 0741 QPointer<KSelectTransactionsDlg> dlg = new KSelectTransactionsDlg(d->m_account, d->m_regForm); 0742 dlg->setWindowTitle(i18n("Select autofill transaction")); 0743 0744 QMap<QString, struct uniqTransaction>::const_iterator it_u; 0745 for (it_u = uniqList.constBegin(); it_u != uniqList.constEnd(); ++it_u) { 0746 dlg->addTransaction(*(*it_u).t); 0747 } 0748 0749 auto tRegister = dlg->getRegister(); 0750 // setup sort order 0751 tRegister->setSortOrder("1,-9,-4"); 0752 // sort the transactions according to the sort setting 0753 tRegister->sortItems(); 0754 0755 // and select the last item 0756 if (tRegister->lastItem()) 0757 tRegister->selectItem(tRegister->lastItem()); 0758 0759 if (dlg->exec() == QDialog::Accepted) { 0760 t = dlg->transaction(); 0761 } 0762 #if 0 0763 } 0764 #endif 0765 } else { 0766 int maxCnt = 0; 0767 QMap<QString, struct uniqTransaction>::const_iterator it_u; 0768 for (it_u = uniqList.constBegin(); it_u != uniqList.constEnd(); ++it_u) { 0769 if ((*it_u).cnt > maxCnt) { 0770 t = *(*it_u).t; 0771 maxCnt = (*it_u).cnt; 0772 } 0773 } 0774 } 0775 0776 if (t != MyMoneyTransaction()) { 0777 d->m_transaction.removeSplits(); 0778 d->m_split = MyMoneySplit(); 0779 MyMoneySplit otherSplit; 0780 foreach (const auto split, t.splits()) { 0781 MyMoneySplit s(split); 0782 s.setReconcileFlag(eMyMoney::Split::State::NotReconciled); 0783 s.setReconcileDate(QDate()); 0784 s.clearId(); 0785 s.setBankID(QString()); 0786 // older versions of KMyMoney used to set the action 0787 // we don't need this anymore 0788 if (s.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization) 0789 && s.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { 0790 s.setAction(QString()); 0791 } 0792 0793 // FIXME update check number. The old comment contained 0794 // 0795 // <quote> 0796 // If a check number is already specified by the user it is 0797 // used. If the input field is empty and the previous transaction 0798 // contains a checknumber, the next usable check number will be assigned 0799 // to the transaction. 0800 // </quote> 0801 0802 auto editNr = dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number")); 0803 if (editNr && !editNr->text().isEmpty()) { 0804 s.setNumber(editNr->text()); 0805 } else if (!s.number().isEmpty()) { 0806 s.setNumber(KMyMoneyUtils::nextFreeCheckNumber(d->m_account)); 0807 } 0808 0809 // if the memos should not be used with autofill or 0810 // if the transaction has exactly two splits, remove 0811 // the memo text of the split that does not reference 0812 // the current account. This allows the user to change 0813 // the autofilled memo text which will then also be used 0814 // in this split. See createTransaction() for this logic. 0815 if ((s.accountId() != d->m_account.id() && t.splitCount() == 2) || !KMyMoneySettings::autoFillUseMemos()) 0816 s.setMemo(QString()); 0817 0818 d->m_transaction.addSplit(s); 0819 if (s.accountId() == d->m_account.id() && d->m_split == MyMoneySplit()) { 0820 d->m_split = s; 0821 } else { 0822 otherSplit = s; 0823 } 0824 } 0825 0826 // make sure to extract the right action 0827 eRegister::Action action; 0828 action = d->m_split.shares().isNegative() ? eRegister::Action::Withdrawal : eRegister::Action::Deposit; 0829 0830 if (d->m_transaction.splitCount() == 2) { 0831 auto acc = MyMoneyFile::instance()->account(otherSplit.accountId()); 0832 if (acc.isAssetLiability()) 0833 action = eRegister::Action::Transfer; 0834 } 0835 0836 // now setup the widgets with the new data but keep the date 0837 if (auto postdateWidget = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"])) { 0838 auto date = postdateWidget->date(); 0839 loadEditWidgets(action); 0840 postdateWidget->setDate(date); 0841 } 0842 } 0843 } 0844 0845 // focus jumps into the category field 0846 QWidget* w; 0847 if ((w = haveWidget("payee")) != 0) { 0848 w->setFocus(); 0849 } 0850 } 0851 0852 void StdTransactionEditor::slotUpdateAction(int action) 0853 { 0854 Q_D(StdTransactionEditor); 0855 auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar")); 0856 if (tabbar) { 0857 auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label")); 0858 auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(d->m_editWidgets["cashflow"]); 0859 if (!categoryLabel || !cashflow) 0860 return; 0861 switch (action) { 0862 case (int)eRegister::Action::Deposit: 0863 categoryLabel->setText(i18n("Category")); 0864 cashflow->setDirection(eRegister::CashFlowDirection::Deposit); 0865 break; 0866 case (int)eRegister::Action::Transfer: 0867 if (d->m_split.shares().isNegative()) { 0868 cashflow->setDirection(eRegister::CashFlowDirection::Payment); 0869 categoryLabel->setText(i18n("Transfer to")); 0870 } else { 0871 cashflow->setDirection(eRegister::CashFlowDirection::Deposit); 0872 categoryLabel->setText(i18n("Transfer from")); 0873 } 0874 tabbar->setCurrentIndex((int)eRegister::Action::Transfer); 0875 slotUpdateCashFlow(cashflow->direction()); 0876 break; 0877 case (int)eRegister::Action::Withdrawal: 0878 categoryLabel->setText(i18n("Category")); 0879 cashflow->setDirection(eRegister::CashFlowDirection::Payment); 0880 break; 0881 } 0882 resizeForm(); 0883 } 0884 } 0885 0886 void StdTransactionEditor::slotUpdateCashFlow(eRegister::CashFlowDirection dir) 0887 { 0888 auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label")); 0889 if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow"))) 0890 cashflow->setDirection(dir); 0891 // qDebug("Update cashflow to %d", dir); 0892 if (categoryLabel) { 0893 auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar")); 0894 if (!tabbar) return; // no transaction form 0895 if (categoryLabel->text() != i18n("Category")) { 0896 tabbar->setCurrentIndex((int)eRegister::Action::Transfer); 0897 if (dir == eRegister::CashFlowDirection::Deposit) { 0898 categoryLabel->setText(i18n("Transfer from")); 0899 } else { 0900 categoryLabel->setText(i18n("Transfer to")); 0901 } 0902 resizeForm(); 0903 } else { 0904 if (dir == eRegister::CashFlowDirection::Deposit) 0905 tabbar->setCurrentIndex((int)eRegister::Action::Deposit); 0906 else 0907 tabbar->setCurrentIndex((int)eRegister::Action::Withdrawal); 0908 } 0909 } 0910 } 0911 0912 void StdTransactionEditor::slotUpdateCategory(const QString& id) 0913 { 0914 Q_D(StdTransactionEditor); 0915 auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label")); 0916 // qDebug("Update category to %s", qPrintable(id)); 0917 0918 if (categoryLabel) { 0919 auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar")); 0920 auto amount = dynamic_cast<AmountEdit*>(d->m_editWidgets["amount"]); 0921 auto val = amount ? amount->value() : MyMoneyMoney(); 0922 0923 if (categoryLabel->text() == i18n("Transfer from")) { 0924 val = -val; 0925 } else { 0926 val = val.abs(); 0927 } 0928 0929 if (tabbar) { 0930 tabbar->setTabEnabled((int)eRegister::Action::Transfer, true); 0931 tabbar->setTabEnabled((int)eRegister::Action::Deposit, true); 0932 tabbar->setTabEnabled((int)eRegister::Action::Withdrawal, true); 0933 } 0934 0935 bool disableTransferTab = false; 0936 if (!id.isEmpty()) { 0937 auto acc = MyMoneyFile::instance()->account(id); 0938 if (acc.isAssetLiability() 0939 || acc.accountGroup() == eMyMoney::Account::Type::Equity) { 0940 if (tabbar) { 0941 tabbar->setCurrentIndex((int)eRegister::Action::Transfer); 0942 tabbar->setTabEnabled((int)eRegister::Action::Deposit, false); 0943 tabbar->setTabEnabled((int)eRegister::Action::Withdrawal, false); 0944 } 0945 auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(d->m_editWidgets["cashflow"]); 0946 if (val.isZero()) { 0947 if (cashflow && (cashflow->direction() == eRegister::CashFlowDirection::Deposit)) { 0948 categoryLabel->setText(i18n("Transfer from")); 0949 } else { 0950 categoryLabel->setText(i18n("Transfer to")); 0951 } 0952 } else if (val.isNegative()) { 0953 categoryLabel->setText(i18n("Transfer from")); 0954 if (cashflow) 0955 cashflow->setDirection(eRegister::CashFlowDirection::Deposit); 0956 } else 0957 categoryLabel->setText(i18n("Transfer to")); 0958 } else { 0959 disableTransferTab = true; 0960 categoryLabel->setText(i18n("Category")); 0961 } 0962 updateAmount(val); 0963 } else { //id.isEmpty() 0964 if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) 0965 disableTransferTab = !category->currentText().isEmpty(); 0966 categoryLabel->setText(i18n("Category")); 0967 } 0968 if (tabbar) { 0969 if (disableTransferTab) { 0970 // set the proper tab before disabling the currently active tab 0971 if (tabbar->currentIndex() == (int)eRegister::Action::Transfer) { 0972 tabbar->setCurrentIndex(val.isPositive() ? (int)eRegister::Action::Withdrawal : (int)eRegister::Action::Deposit); 0973 } 0974 tabbar->setTabEnabled((int)eRegister::Action::Transfer, false); 0975 } 0976 tabbar->update(); 0977 } 0978 0979 resizeForm(); 0980 } 0981 updateVAT(false); 0982 } 0983 0984 void StdTransactionEditor::slotUpdatePayment(const QString& txt) 0985 { 0986 Q_D(StdTransactionEditor); 0987 MyMoneyMoney val(txt); 0988 0989 auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]); 0990 auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]); 0991 if (!depositWidget || !paymentWidget) 0992 return; 0993 0994 if (val.isNegative()) { 0995 depositWidget->setValue(val.abs()); 0996 paymentWidget->setText(QString()); 0997 } else { 0998 depositWidget->setText(QString()); 0999 } 1000 updateVAT(); 1001 } 1002 1003 void StdTransactionEditor::slotUpdateDeposit(const QString& txt) 1004 { 1005 Q_D(StdTransactionEditor); 1006 MyMoneyMoney val(txt); 1007 1008 auto depositWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]); 1009 auto paymentWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]); 1010 if (!depositWidget || !paymentWidget) 1011 return; 1012 1013 if (val.isNegative()) { 1014 paymentWidget->setValue(val.abs()); 1015 depositWidget->setText(QString()); 1016 } else { 1017 paymentWidget->setText(QString()); 1018 } 1019 updateVAT(); 1020 } 1021 1022 void StdTransactionEditor::slotUpdateAmount(const QString& txt) 1023 { 1024 // qDebug("Update amount to %s", qPrintable(txt)); 1025 MyMoneyMoney val(txt); 1026 updateAmount(val); 1027 updateVAT(true); 1028 } 1029 1030 void StdTransactionEditor::updateAmount(const MyMoneyMoney& val) 1031 { 1032 // we don't do anything if we have multiple transactions selected 1033 if (isMultiSelection()) 1034 return; 1035 1036 Q_D(StdTransactionEditor); 1037 auto categoryLabel = dynamic_cast<QLabel*>(haveWidget("category-label")); 1038 if (categoryLabel) { 1039 if (auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(d->m_editWidgets["cashflow"])) { 1040 QSignalBlocker blockCashFlow(cashflow); 1041 if (val.isNegative()) { 1042 cashflow->reverseDirection(); 1043 } 1044 slotUpdateCashFlow(cashflow->direction()); 1045 if (auto amountWidget = dynamic_cast<AmountEdit*>(d->m_editWidgets["amount"])) 1046 amountWidget->setValue(val.abs()); 1047 } 1048 } 1049 } 1050 1051 void StdTransactionEditor::updateVAT(bool amountChanged) 1052 { 1053 Q_D(StdTransactionEditor); 1054 // make sure that we don't do this recursively 1055 if (d->m_inUpdateVat) 1056 return; 1057 1058 // we don't do anything if we have multiple transactions selected 1059 if (isMultiSelection()) 1060 return; 1061 1062 // if auto vat assignment for this account is turned off 1063 // we don't care about taxes 1064 if (d->m_account.value("NoVat") == "Yes") 1065 return; 1066 1067 // more splits than category and tax are not supported 1068 if (d->m_splits.count() > 2) 1069 return; 1070 1071 // in order to do anything, we need an amount 1072 MyMoneyMoney amount, newAmount; 1073 bool amountOk; 1074 amount = amountFromWidget(&amountOk); 1075 if (!amountOk) 1076 return; 1077 1078 // If the transaction has a tax and a category split, remove the tax split 1079 if (d->m_splits.count() == 2) { 1080 newAmount = removeVatSplit(); 1081 if (d->m_splits.count() == 2) // not removed? 1082 return; 1083 1084 } else if (auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"])) { 1085 // otherwise, we need a category 1086 if (category->selectedItem().isEmpty()) 1087 return; 1088 1089 // if no VAT account is associated with this category/account, then we bail out 1090 MyMoneyAccount cat = MyMoneyFile::instance()->account(category->selectedItem()); 1091 if (cat.value("VatAccount").isEmpty()) 1092 return; 1093 1094 newAmount = amount; 1095 } 1096 1097 // seems we have everything we need 1098 if (amountChanged) 1099 newAmount = amount; 1100 1101 MyMoneyTransaction transaction; 1102 if (createTransaction(transaction, d->m_transaction, d->m_split)) { 1103 if (addVatSplit(transaction, newAmount)) { 1104 d->m_transaction = transaction; 1105 if (!d->m_transaction.splits().isEmpty()) 1106 d->m_split = d->m_transaction.splits().front(); 1107 1108 loadEditWidgets(); 1109 1110 // if we made this a split transaction, then move the 1111 // focus to the memo field 1112 if (qApp->focusWidget() == haveWidget("category")) { 1113 QWidget* w = haveWidget("memo"); 1114 if (w) 1115 w->setFocus(); 1116 } 1117 } 1118 } 1119 } 1120 1121 bool StdTransactionEditor::addVatSplit(MyMoneyTransaction& tr, const MyMoneyMoney& amount) 1122 { 1123 if (tr.splitCount() != 2) 1124 return false; 1125 1126 Q_D(StdTransactionEditor); 1127 auto file = MyMoneyFile::instance(); 1128 // extract the category split from the transaction 1129 MyMoneyAccount category = file->account(tr.splitByAccount(d->m_account.id(), false).accountId()); 1130 return file->addVATSplit(tr, d->m_account, category, amount); 1131 } 1132 1133 MyMoneyMoney StdTransactionEditor::removeVatSplit() 1134 { 1135 Q_D(StdTransactionEditor); 1136 // we only deal with splits that have three splits 1137 if (d->m_splits.count() != 2) 1138 return amountFromWidget(); 1139 1140 MyMoneySplit c; // category split 1141 MyMoneySplit t; // tax split 1142 1143 auto netValue = false; 1144 foreach (const auto split, d->m_splits) { 1145 auto acc = MyMoneyFile::instance()->account(split.accountId()); 1146 if (!acc.value("VatAccount").isEmpty()) { 1147 netValue = (acc.value("VatAmount").toLower() == "net"); 1148 c = split; 1149 } else if (!acc.value("VatRate").isEmpty()) { 1150 t = split; 1151 } 1152 } 1153 1154 // bail out if not all splits are setup 1155 if (c.id().isEmpty() || t.id().isEmpty()) 1156 return amountFromWidget(); 1157 1158 MyMoneyMoney amount; 1159 // reduce the splits 1160 if (netValue) { 1161 amount = -c.shares(); 1162 } else { 1163 amount = -(c.shares() + t.shares()); 1164 } 1165 1166 // remove tax split from the list, ... 1167 d->m_splits.clear(); 1168 d->m_splits.append(c); 1169 1170 // ... make sure that the widget is updated ... 1171 // block the signals to avoid popping up the split editor dialog 1172 // for nothing 1173 d->m_editWidgets["category"]->blockSignals(true); 1174 QString id; 1175 setupCategoryWidget(id); 1176 d->m_editWidgets["category"]->blockSignals(false); 1177 1178 // ... and return the updated amount 1179 return amount; 1180 } 1181 1182 bool StdTransactionEditor::isComplete(QString& reason) const 1183 { 1184 Q_D(const StdTransactionEditor); 1185 1186 if (d->m_readOnly) { 1187 reason = i18n( 1188 "At least one split of the selected transaction references an account that has been closed. " 1189 "Editing the transactions is therefore prohibited."); 1190 return false; 1191 } 1192 1193 reason.clear(); 1194 QMap<QString, QWidget*>::const_iterator it_w; 1195 1196 auto postDate = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"]); 1197 if (postDate) { 1198 QDate accountOpeningDate = d->m_account.openingDate(); 1199 for (QList<MyMoneySplit>::const_iterator it_s = d->m_splits.constBegin(); it_s != d->m_splits.constEnd(); ++it_s) { 1200 const MyMoneyAccount& acc = MyMoneyFile::instance()->account((*it_s).accountId()); 1201 // compute the newest opening date of all accounts involved in the transaction 1202 if (acc.openingDate() > accountOpeningDate) 1203 accountOpeningDate = acc.openingDate(); 1204 } 1205 // check the selected category in case m_splits hasn't been updated yet 1206 auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]); 1207 if (category && !category->selectedItem().isEmpty()) { 1208 MyMoneyAccount cat = MyMoneyFile::instance()->account(category->selectedItem()); 1209 if (cat.openingDate() > accountOpeningDate) 1210 accountOpeningDate = cat.openingDate(); 1211 } 1212 1213 if (postDate->date().isValid() && (postDate->date() < accountOpeningDate)) { 1214 postDate->markAsBadDate(true, KMyMoneySettings::schemeColor(SchemeColor::Negative)); 1215 reason = i18n("Cannot enter transaction with postdate prior to account's opening date."); 1216 postDate->setToolTip(reason); 1217 return false; 1218 } 1219 postDate->markAsBadDate(); 1220 postDate->setToolTip(QString()); 1221 } 1222 1223 for (it_w = d->m_editWidgets.begin(); it_w != d->m_editWidgets.end(); ++it_w) { 1224 auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(*it_w); 1225 auto tagContainer = dynamic_cast<KTagContainer*>(*it_w); 1226 auto category = dynamic_cast<KMyMoneyCategory*>(*it_w); 1227 auto amount = dynamic_cast<AmountEdit*>(*it_w); 1228 auto reconcile = dynamic_cast<KMyMoneyReconcileCombo*>(*it_w); 1229 auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(*it_w); 1230 auto memo = dynamic_cast<KTextEdit*>(*it_w); 1231 1232 if (payee && !(payee->currentText().isEmpty())) 1233 break; 1234 1235 if (category && !category->lineEdit()->text().isEmpty()) 1236 break; 1237 1238 if (amount && !(amount->value().isZero())) 1239 break; 1240 1241 // the following widgets are only checked if we are editing multiple transactions 1242 if (isMultiSelection()) { 1243 if (auto tabbar = dynamic_cast<KMyMoneyTransactionForm::TabBar*>(haveWidget("tabbar"))) 1244 tabbar->setEnabled(true); 1245 1246 if (reconcile && reconcile->state() != eMyMoney::Split::State::Unknown) 1247 break; 1248 1249 if (cashflow && cashflow->direction() != eRegister::CashFlowDirection::Unknown) 1250 break; 1251 1252 if (postDate && postDate->date().isValid() && (postDate->date() >= d->m_account.openingDate())) 1253 break; 1254 1255 if (memo && d->m_memoChanged) 1256 break; 1257 1258 if (tagContainer && !(tagContainer->selectedTags().isEmpty())) // Tag is optional field 1259 break; 1260 } 1261 } 1262 return it_w != d->m_editWidgets.end(); 1263 } 1264 1265 void StdTransactionEditor::slotCreateCategory(const QString& name, QString& id) 1266 { 1267 Q_D(StdTransactionEditor); 1268 MyMoneyAccount acc, parent; 1269 acc.setName(name); 1270 1271 auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow")); 1272 if (cashflow) { 1273 // form based input 1274 if (cashflow->direction() == eRegister::CashFlowDirection::Deposit) 1275 parent = MyMoneyFile::instance()->income(); 1276 else 1277 parent = MyMoneyFile::instance()->expense(); 1278 1279 } else if (haveWidget("deposit")) { 1280 // register based input 1281 if (auto deposit = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"])) { 1282 if (deposit->value().isPositive()) 1283 parent = MyMoneyFile::instance()->income(); 1284 else 1285 parent = MyMoneyFile::instance()->expense(); 1286 } 1287 1288 } else 1289 parent = MyMoneyFile::instance()->expense(); 1290 1291 // TODO extract possible first part of a hierarchy and check if it is one 1292 // of our top categories. If so, remove it and select the parent 1293 // according to this information. 1294 1295 slotNewCategory(acc, parent); 1296 1297 // return id 1298 id = acc.id(); 1299 } 1300 1301 int StdTransactionEditor::slotEditSplits() 1302 { 1303 Q_D(StdTransactionEditor); 1304 int rc = QDialog::Rejected; 1305 1306 if (!d->m_openEditSplits) { 1307 // only get in here in a single instance 1308 d->m_openEditSplits = true; 1309 1310 // force focus change to update all data 1311 auto categoryWidget = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]); 1312 QWidget* w = categoryWidget ? categoryWidget->splitButton() : nullptr; 1313 if (w) 1314 w->setFocus(); 1315 1316 auto amount = dynamic_cast<AmountEdit*>(haveWidget("amount")); 1317 auto deposit = dynamic_cast<AmountEdit*>(haveWidget("deposit")); 1318 auto payment = dynamic_cast<AmountEdit*>(haveWidget("payment")); 1319 KMyMoneyCashFlowCombo* cashflow = 0; 1320 eRegister::CashFlowDirection dir = eRegister::CashFlowDirection::Unknown; 1321 bool isValidAmount = false; 1322 1323 if (amount) { 1324 isValidAmount = amount->text().length() != 0; 1325 if ((cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow")))) 1326 dir = cashflow->direction(); 1327 1328 } else { 1329 if (deposit) { 1330 if (deposit->text().length() != 0) { 1331 isValidAmount = true; 1332 dir = eRegister::CashFlowDirection::Deposit; 1333 } 1334 } 1335 if (payment) { 1336 if (payment->text().length() != 0) { 1337 isValidAmount = true; 1338 dir = eRegister::CashFlowDirection::Payment; 1339 } 1340 } 1341 if (!deposit || !payment) { 1342 qDebug("Internal error: deposit(%p) & payment(%p) widgets not found but required", deposit, payment); 1343 return rc; 1344 } 1345 } 1346 1347 if (dir == eRegister::CashFlowDirection::Unknown) 1348 dir = eRegister::CashFlowDirection::Payment; 1349 1350 MyMoneyTransaction transaction; 1351 if (createTransaction(transaction, d->m_transaction, d->m_split)) { 1352 MyMoneyMoney value; 1353 1354 QPointer<KSplitTransactionDlg> dlg = 1355 new KSplitTransactionDlg(transaction, 1356 transaction.splits().isEmpty() ? MyMoneySplit() : transaction.splits().front(), 1357 d->m_account, 1358 isValidAmount, 1359 dir == eRegister::CashFlowDirection::Deposit, 1360 MyMoneyMoney(), 1361 d->m_priceInfo, 1362 d->m_regForm); 1363 connect(dlg.data(), &KSplitTransactionDlg::objectCreation, this, &StdTransactionEditor::objectCreation); 1364 connect(dlg.data(), &KSplitTransactionDlg::createCategory, this, &StdTransactionEditor::slotNewCategory); 1365 1366 // propagate read-only mode 1367 dlg->setReadOnlyMode(d->m_readOnly); 1368 1369 if ((rc = dlg->exec()) == QDialog::Accepted) { 1370 d->m_transaction = dlg->transaction(); 1371 if (!d->m_transaction.splits().isEmpty()) { 1372 d->m_split = d->m_transaction.splits().front(); 1373 // if we have only two splits left, we copy the memo 1374 // of the second (data from the split editor) to the 1375 // first (data used in the transaction editor) 1376 if (d->m_transaction.splitCount() == 2) { 1377 d->m_split.setMemo(d->m_transaction.splits().last().memo()); 1378 d->m_transaction.modifySplit(d->m_split); 1379 } 1380 } 1381 loadEditWidgets(); 1382 } 1383 1384 delete dlg; 1385 } 1386 1387 // focus jumps into the tag field 1388 if ((w = haveWidget("tag")) != 0) { 1389 w->setFocus(); 1390 } 1391 1392 d->m_openEditSplits = false; 1393 } 1394 1395 return rc; 1396 } 1397 1398 void StdTransactionEditor::checkPayeeInSplit(MyMoneySplit& s, const QString& payeeId) 1399 { 1400 if (s.accountId().isEmpty()) 1401 return; 1402 1403 auto acc = MyMoneyFile::instance()->account(s.accountId()); 1404 if (acc.isIncomeExpense()) { 1405 s.setPayeeId(payeeId); 1406 } else { 1407 if (s.payeeId().isEmpty()) 1408 s.setPayeeId(payeeId); 1409 } 1410 } 1411 1412 MyMoneyMoney StdTransactionEditor::amountFromWidget(bool* update) const 1413 { 1414 Q_D(const StdTransactionEditor); 1415 bool updateValue = false; 1416 MyMoneyMoney value; 1417 1418 auto cashflow = dynamic_cast<KMyMoneyCashFlowCombo*>(haveWidget("cashflow")); 1419 if (cashflow) { 1420 // form based input 1421 if (auto amount = dynamic_cast<AmountEdit*>(d->m_editWidgets["amount"])) { 1422 // if both fields do not contain changes -> no need to update 1423 if (cashflow->direction() != eRegister::CashFlowDirection::Unknown 1424 && !amount->text().isEmpty()) 1425 updateValue = true; 1426 value = amount->value(); 1427 if (cashflow->direction() == eRegister::CashFlowDirection::Payment) 1428 value = -value; 1429 } 1430 1431 } else if (haveWidget("deposit")) { 1432 // register based input 1433 auto deposit = dynamic_cast<AmountEdit*>(d->m_editWidgets["deposit"]); 1434 auto payment = dynamic_cast<AmountEdit*>(d->m_editWidgets["payment"]); 1435 if (deposit && payment) { 1436 // if both fields do not contain text -> no need to update 1437 if (!(deposit->text().isEmpty() && payment->text().isEmpty())) 1438 updateValue = true; 1439 1440 if (deposit->value().isPositive()) 1441 value = deposit->value(); 1442 else 1443 value = -(payment->value()); 1444 } 1445 } 1446 1447 if (update) 1448 *update = updateValue; 1449 1450 // determine the max fraction for this account and 1451 // adjust the value accordingly 1452 return value.convert(d->m_account.fraction()); 1453 } 1454 1455 bool StdTransactionEditor::createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool skipPriceDialog) 1456 { 1457 Q_D(StdTransactionEditor); 1458 // extract price info from original transaction 1459 d->m_priceInfo.clear(); 1460 if (!torig.id().isEmpty()) { 1461 foreach (const auto split, torig.splits()) { 1462 if (split.id() != sorig.id()) { 1463 MyMoneyAccount cat = MyMoneyFile::instance()->account(split.accountId()); 1464 if (cat.currencyId() != d->m_account.currencyId()) { 1465 if (!split.shares().isZero() && !split.value().isZero()) { 1466 d->m_priceInfo[cat.currencyId()] = (split.shares() / split.value()).reduce(); 1467 } 1468 } 1469 } 1470 } 1471 } 1472 1473 t = torig; 1474 1475 t.removeSplits(); 1476 t.setCommodity(d->m_account.currencyId()); 1477 1478 auto postDate = dynamic_cast<KMyMoneyDateInput*>(d->m_editWidgets["postdate"]); 1479 if (postDate && postDate->date().isValid()) { 1480 t.setPostDate(postDate->date()); 1481 } 1482 1483 // we start with the previous values, make sure we can add them later on 1484 MyMoneySplit s0 = sorig; 1485 s0.clearId(); 1486 1487 // make sure we reference this account here 1488 s0.setAccountId(d->m_account.id()); 1489 1490 // memo and number field are special: if we have multiple transactions selected 1491 // and the edit field is empty, we treat it as "not modified". 1492 // FIXME a better approach would be to have a 'dirty' flag with the widgets 1493 // which identifies if the originally loaded value has been modified 1494 // by the user 1495 auto memo = dynamic_cast<KTextEdit*>(d->m_editWidgets["memo"]); 1496 if (memo) { 1497 if (!isMultiSelection() || d->m_memoChanged) 1498 s0.setMemo(memo->toPlainText()); 1499 } 1500 1501 if (auto number = dynamic_cast<KMyMoneyLineEdit*>(haveWidget("number"))) { 1502 if (!isMultiSelection() || !number->text().isEmpty()) 1503 s0.setNumber(number->text()); 1504 } 1505 1506 auto payee = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_editWidgets["payee"]); 1507 QString payeeId; 1508 if (payee && (!isMultiSelection() || !payee->currentText().isEmpty())) { 1509 payeeId = payee->selectedItem(); 1510 s0.setPayeeId(payeeId); 1511 } 1512 1513 //KMyMoneyTagCombo* tag = dynamic_cast<KMyMoneyTagCombo*>(m_editWidgets["tag"]); 1514 auto tag = dynamic_cast<KTagContainer*>(d->m_editWidgets["tag"]); 1515 if (tag && (!isMultiSelection() || !tag->selectedTags().isEmpty())) { 1516 s0.setTagIdList(tag->selectedTags()); 1517 } 1518 1519 bool updateValue; 1520 MyMoneyMoney value = amountFromWidget(&updateValue); 1521 1522 if (updateValue) { 1523 // for this account, the shares and value is the same 1524 s0.setValue(value); 1525 s0.setShares(value); 1526 } else { 1527 value = s0.value(); 1528 } 1529 1530 // if we mark the split reconciled here, we'll use today's date if no reconciliation date is given 1531 auto status = dynamic_cast<KMyMoneyReconcileCombo*>(d->m_editWidgets["status"]); 1532 if (status && status->state() != eMyMoney::Split::State::Unknown) 1533 s0.setReconcileFlag(status->state()); 1534 1535 if (s0.reconcileFlag() == eMyMoney::Split::State::Reconciled && !s0.reconcileDate().isValid()) 1536 s0.setReconcileDate(QDate::currentDate()); 1537 1538 checkPayeeInSplit(s0, payeeId); 1539 1540 // add the split to the transaction 1541 t.addSplit(s0); 1542 1543 // if we have no other split we create it 1544 // if we have none or only one other split, we reconstruct it here 1545 // if we have more than one other split, we take them as they are 1546 // make sure to perform all those changes on a local copy 1547 QList<MyMoneySplit> splits = d->m_splits; 1548 1549 MyMoneySplit s1; 1550 if (splits.isEmpty()) { 1551 s1.setMemo(s0.memo()); 1552 splits.append(s1); 1553 1554 // make sure we will fill the value and share fields later on 1555 updateValue = true; 1556 } 1557 1558 // FIXME in multiSelection we currently only support transactions with one 1559 // or two splits. So we check the original transaction and extract the other 1560 // split or create it 1561 if (isMultiSelection()) { 1562 if (torig.splitCount() == 2) { 1563 foreach (const auto split, torig.splits()) { 1564 if (split.id() == sorig.id()) 1565 continue; 1566 s1 = split; 1567 s1.clearId(); 1568 break; 1569 } 1570 } 1571 } else { 1572 if (splits.count() == 1) { 1573 s1 = splits[0]; 1574 s1.clearId(); 1575 } 1576 } 1577 1578 if (isMultiSelection() || splits.count() == 1) { 1579 auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]); 1580 if (category && (!isMultiSelection() || !category->currentText().isEmpty())) { 1581 s1.setAccountId(category->selectedItem()); 1582 } 1583 1584 // if the first split has a memo but the second split is empty, 1585 // we just copy the memo text over 1586 if (memo) { 1587 if (!isMultiSelection() || !memo->toPlainText().isEmpty()) { 1588 // if the memo is filled, we check if the 1589 // account referenced by s1 is a regular account or a category. 1590 // in case of a regular account, we just leave the memo as is 1591 // in case of a category we simply copy the new value over the old. 1592 // in case we don't even have an account id, we just skip because 1593 // the split will be removed later on anyway. 1594 if (!s1.memo().isEmpty() && s1.memo() != s0.memo()) { 1595 if (!s1.accountId().isEmpty()) { 1596 auto acc = MyMoneyFile::instance()->account(s1.accountId()); 1597 if (acc.isIncomeExpense()) 1598 s1.setMemo(s0.memo()); 1599 else if (KMessageBox::questionYesNo(d->m_regForm, 1600 i18n("Do you want to replace memo<p><i>%1</i></p>with memo<p><i>%2</i></p>in the other split?", s1.memo(), s0.memo()), i18n("Copy memo"), 1601 KStandardGuiItem::yes(), KStandardGuiItem::no(), 1602 QStringLiteral("CopyMemoOver")) == KMessageBox::Yes) 1603 s1.setMemo(s0.memo()); 1604 } 1605 } else { 1606 s1.setMemo(s0.memo()); 1607 } 1608 } 1609 } 1610 1611 if (updateValue && !s1.accountId().isEmpty()) { 1612 s1.setValue(-value); 1613 MyMoneyMoney shares; 1614 if (!skipPriceDialog) { 1615 if (!KCurrencyCalculator::setupSplitPrice(shares, t, s1, d->m_priceInfo, d->m_regForm)) 1616 return false; 1617 } else { 1618 MyMoneyAccount cat = MyMoneyFile::instance()->account(s1.accountId()); 1619 if (d->m_priceInfo.find(cat.currencyId()) != d->m_priceInfo.end()) { 1620 shares = (s1.value() * d->m_priceInfo[cat.currencyId()]).reduce().convert(cat.fraction()); 1621 } else 1622 shares = s1.value(); 1623 } 1624 s1.setShares(shares); 1625 } 1626 1627 checkPayeeInSplit(s1, payeeId); 1628 1629 if (!s1.accountId().isEmpty()) 1630 t.addSplit(s1); 1631 1632 // check if we need to add/update a VAT assignment 1633 MyMoneyFile::instance()->updateVAT(t); 1634 1635 } else { 1636 foreach (const auto split, splits) { 1637 s1 = split; 1638 s1.clearId(); 1639 checkPayeeInSplit(s1, payeeId); 1640 t.addSplit(s1); 1641 } 1642 } 1643 return true; 1644 } 1645 1646 void StdTransactionEditor::setupFinalWidgets() 1647 { 1648 addFinalWidget(haveWidget("deposit")); 1649 addFinalWidget(haveWidget("payment")); 1650 addFinalWidget(haveWidget("amount")); 1651 addFinalWidget(haveWidget("status")); 1652 } 1653 1654 void StdTransactionEditor::slotUpdateAccount(const QString& id) 1655 { 1656 Q_D(StdTransactionEditor); 1657 TransactionEditor::slotUpdateAccount(id); 1658 auto category = dynamic_cast<KMyMoneyCategory*>(d->m_editWidgets["category"]); 1659 if (category && category->splitButton()) { 1660 category->splitButton()->setDisabled(id.isEmpty()); 1661 } 1662 }