File indexing completed on 2024-05-12 16:43:53

0001 /*
0002     SPDX-FileCopyrightText: 2016 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "newspliteditor.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QCompleter>
0012 #include <QSortFilterProxyModel>
0013 #include <QStringList>
0014 #include <QDebug>
0015 #include <QStandardItemModel>
0016 
0017 // ----------------------------------------------------------------------------
0018 // KDE Includes
0019 
0020 #include <KLocalizedString>
0021 
0022 // ----------------------------------------------------------------------------
0023 // Project Includes
0024 
0025 #include "creditdebithelper.h"
0026 #include "kmymoneyutils.h"
0027 #include "kmymoneyaccountcombo.h"
0028 #include "models.h"
0029 #include "accountsmodel.h"
0030 #include "costcentermodel.h"
0031 #include "ledgermodel.h"
0032 #include "splitmodel.h"
0033 #include "mymoneyaccount.h"
0034 #include "mymoneyexception.h"
0035 #include "ui_newspliteditor.h"
0036 #include "widgethintframe.h"
0037 #include "ledgerview.h"
0038 #include "icons/icons.h"
0039 #include "mymoneyenums.h"
0040 #include "modelenums.h"
0041 
0042 using namespace Icons;
0043 
0044 struct NewSplitEditor::Private
0045 {
0046     Private(NewSplitEditor* parent)
0047         : ui(new Ui_NewSplitEditor)
0048         , accountsModel(new AccountNamesFilterProxyModel(parent))
0049         , costCenterModel(new QSortFilterProxyModel(parent))
0050         , splitModel(0)
0051         , accepted(false)
0052         , costCenterRequired(false)
0053         , showValuesInverted(false)
0054         , amountHelper(nullptr)
0055     {
0056         accountsModel->setObjectName("AccountNamesFilterProxyModel");
0057         costCenterModel->setObjectName("SortedCostCenterModel");
0058         statusModel.setObjectName("StatusModel");
0059 
0060         costCenterModel->setSortLocaleAware(true);
0061         costCenterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0062 
0063         createStatusEntry(eMyMoney::Split::State::NotReconciled);
0064         createStatusEntry(eMyMoney::Split::State::Cleared);
0065         createStatusEntry(eMyMoney::Split::State::Reconciled);
0066         // createStatusEntry(eMyMoney::Split::State::Frozen);
0067     }
0068 
0069     ~Private()
0070     {
0071         delete ui;
0072     }
0073 
0074     void createStatusEntry(eMyMoney::Split::State status);
0075     bool checkForValidSplit(bool doUserInteraction = true);
0076 
0077     bool costCenterChanged(int costCenterIndex);
0078     bool categoryChanged(const QString& accountId);
0079     bool numberChanged(const QString& newNumber);
0080     bool amountChanged(CreditDebitHelper* valueHelper);
0081 
0082     Ui_NewSplitEditor*            ui;
0083     AccountNamesFilterProxyModel* accountsModel;
0084     QSortFilterProxyModel*        costCenterModel;
0085     SplitModel*                   splitModel;
0086     bool                          accepted;
0087     bool                          costCenterRequired;
0088     bool                          showValuesInverted;
0089     QStandardItemModel            statusModel;
0090     QString                       transactionSplitId;
0091     MyMoneyAccount                counterAccount;
0092     MyMoneyAccount                category;
0093     CreditDebitHelper*            amountHelper;
0094 };
0095 
0096 void NewSplitEditor::Private::createStatusEntry(eMyMoney::Split::State status)
0097 {
0098     QStandardItem* p = new QStandardItem(KMyMoneyUtils::reconcileStateToString(status, true));
0099     p->setData((int)status);
0100     statusModel.appendRow(p);
0101 }
0102 
0103 bool NewSplitEditor::Private::checkForValidSplit(bool doUserInteraction)
0104 {
0105     QStringList infos;
0106     bool rc = true;
0107     if(!costCenterChanged(ui->costCenterCombo->currentIndex())) {
0108         infos << ui->costCenterCombo->toolTip();
0109         rc = false;
0110     }
0111 
0112     if(doUserInteraction) {
0113         /// @todo add dialog here that shows the @a infos
0114     }
0115     return rc;
0116 }
0117 
0118 bool NewSplitEditor::Private::costCenterChanged(int costCenterIndex)
0119 {
0120     bool rc = true;
0121     WidgetHintFrame::hide(ui->costCenterCombo, i18n("The cost center this transaction should be assigned to."));
0122     if(costCenterIndex != -1) {
0123         if(costCenterRequired && ui->costCenterCombo->currentText().isEmpty()) {
0124             WidgetHintFrame::show(ui->costCenterCombo, i18n("A cost center assignment is required for a transaction in the selected category."));
0125             rc = false;
0126         }
0127     }
0128     return rc;
0129 }
0130 
0131 bool NewSplitEditor::Private::categoryChanged(const QString& accountId)
0132 {
0133     bool rc = true;
0134     if(!accountId.isEmpty()) {
0135         try {
0136             QModelIndex index = Models::instance()->accountsModel()->accountById(accountId);
0137             category = Models::instance()->accountsModel()->data(index, (int)eAccountsModel::Role::Account).value<MyMoneyAccount>();
0138             const bool isIncomeExpense = category.isIncomeExpense();
0139             ui->costCenterCombo->setEnabled(isIncomeExpense);
0140             ui->costCenterLabel->setEnabled(isIncomeExpense);
0141             ui->numberEdit->setDisabled(isIncomeExpense);
0142             ui->numberLabel->setDisabled(isIncomeExpense);
0143 
0144             costCenterRequired = category.isCostCenterRequired();
0145             rc &= costCenterChanged(ui->costCenterCombo->currentIndex());
0146         } catch (MyMoneyException &e) {
0147             qDebug() << "Ooops: invalid account id" << accountId << "in" << Q_FUNC_INFO;
0148         }
0149     }
0150     return rc;
0151 }
0152 
0153 bool NewSplitEditor::Private::numberChanged(const QString& newNumber)
0154 {
0155     bool rc = true;
0156     WidgetHintFrame::hide(ui->numberEdit, i18n("The check number used for this transaction."));
0157     if(!newNumber.isEmpty()) {
0158         const LedgerModel* model = Models::instance()->ledgerModel();
0159         QModelIndexList list = model->match(model->index(0, 0), (int)eLedgerModel::Role::Number,
0160                                             QVariant(newNumber),
0161                                             -1,                         // all splits
0162                                             Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
0163 
0164         foreach(QModelIndex index, list) {
0165             if(model->data(index, (int)eLedgerModel::Role::AccountId) == ui->accountCombo->getSelected()
0166                     && model->data(index, (int)eLedgerModel::Role::TransactionSplitId) != transactionSplitId) {
0167                 WidgetHintFrame::show(ui->numberEdit, i18n("The check number <b>%1</b> has already been used in this account.", newNumber));
0168                 rc = false;
0169                 break;
0170             }
0171         }
0172     }
0173     return rc;
0174 }
0175 
0176 bool NewSplitEditor::Private::amountChanged(CreditDebitHelper* valueHelper)
0177 {
0178     Q_UNUSED(valueHelper);
0179     bool rc = true;
0180     return rc;
0181 }
0182 
0183 
0184 
0185 NewSplitEditor::NewSplitEditor(QWidget* parent, const QString& counterAccountId)
0186     : QFrame(parent, Qt::FramelessWindowHint /* | Qt::X11BypassWindowManagerHint */)
0187     , d(new Private(this))
0188 {
0189     SplitView* view = qobject_cast<SplitView*>(parent->parentWidget());
0190     Q_ASSERT(view != 0);
0191     d->splitModel = qobject_cast<SplitModel*>(view->model());
0192 
0193     QModelIndex index = Models::instance()->accountsModel()->accountById(counterAccountId);
0194     d->counterAccount = Models::instance()->accountsModel()->data(index, (int)eAccountsModel::Role::Account).value<MyMoneyAccount>();
0195 
0196     d->ui->setupUi(this);
0197     d->ui->enterButton->setIcon(Icons::get(Icon::DialogOK));
0198     d->ui->cancelButton->setIcon(Icons::get(Icon::DialogCancel));
0199 
0200     d->accountsModel->addAccountGroup(QVector<eMyMoney::Account::Type> {eMyMoney::Account::Type::Asset, eMyMoney::Account::Type::Liability, eMyMoney::Account::Type::Income, eMyMoney::Account::Type::Expense, eMyMoney::Account::Type::Equity,});
0201     d->accountsModel->setHideEquityAccounts(false);
0202     auto const model = Models::instance()->accountsModel();
0203     d->accountsModel->setSourceColumns(model->getColumns());
0204     d->accountsModel->setSourceModel(model);
0205     d->accountsModel->sort((int)eAccountsModel::Column::Account);
0206     d->ui->accountCombo->setModel(d->accountsModel);
0207 
0208     d->costCenterModel->setSortRole(Qt::DisplayRole);
0209     d->costCenterModel->setSourceModel(Models::instance()->costCenterModel());
0210     d->costCenterModel->sort((int)eAccountsModel::Column::Account);
0211 
0212     d->ui->costCenterCombo->setEditable(true);
0213     d->ui->costCenterCombo->setModel(d->costCenterModel);
0214     d->ui->costCenterCombo->setModelColumn(0);
0215     d->ui->costCenterCombo->completer()->setFilterMode(Qt::MatchContains);
0216 
0217     WidgetHintFrameCollection* frameCollection = new WidgetHintFrameCollection(this);
0218     frameCollection->addFrame(new WidgetHintFrame(d->ui->costCenterCombo));
0219     frameCollection->addFrame(new WidgetHintFrame(d->ui->numberEdit, WidgetHintFrame::Warning));
0220     frameCollection->addWidget(d->ui->enterButton);
0221 
0222     d->amountHelper = new CreditDebitHelper(this, d->ui->amountEditCredit, d->ui->amountEditDebit);
0223 
0224     connect(d->ui->numberEdit, SIGNAL(textChanged(QString)), this, SLOT(numberChanged(QString)));
0225     connect(d->ui->costCenterCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(costCenterChanged(int)));
0226     connect(d->ui->accountCombo, SIGNAL(accountSelected(QString)), this, SLOT(categoryChanged(QString)));
0227     connect(d->amountHelper, SIGNAL(valueChanged()), this, SLOT(amountChanged()));
0228 
0229     connect(d->ui->cancelButton, SIGNAL(clicked(bool)), this, SLOT(reject()));
0230     connect(d->ui->enterButton, SIGNAL(clicked(bool)), this, SLOT(acceptEdit()));
0231 }
0232 
0233 NewSplitEditor::~NewSplitEditor()
0234 {
0235 }
0236 
0237 void NewSplitEditor::setShowValuesInverted(bool inverse)
0238 {
0239     d->showValuesInverted = inverse;
0240 }
0241 
0242 bool NewSplitEditor::showValuesInverted()
0243 {
0244     return d->showValuesInverted;
0245 }
0246 
0247 bool NewSplitEditor::accepted() const
0248 {
0249     return d->accepted;
0250 }
0251 
0252 void NewSplitEditor::acceptEdit()
0253 {
0254     if(d->checkForValidSplit()) {
0255         d->accepted = true;
0256         emit done();
0257     }
0258 }
0259 
0260 void NewSplitEditor::reject()
0261 {
0262     emit done();
0263 }
0264 
0265 void NewSplitEditor::keyPressEvent(QKeyEvent* e)
0266 {
0267     if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) {
0268         switch (e->key()) {
0269         case Qt::Key_Enter:
0270         case Qt::Key_Return:
0271         {
0272             if(focusWidget() == d->ui->cancelButton) {
0273                 reject();
0274             } else {
0275                 if(d->ui->enterButton->isEnabled()) {
0276                     d->ui->enterButton->click();
0277                 }
0278                 return;
0279             }
0280         }
0281         break;
0282 
0283         case Qt::Key_Escape:
0284             reject();
0285             break;
0286 
0287         default:
0288             e->ignore();
0289             return;
0290         }
0291     } else {
0292         e->ignore();
0293     }
0294 }
0295 
0296 QString NewSplitEditor::accountId() const
0297 {
0298     return d->ui->accountCombo->getSelected();
0299 }
0300 
0301 void NewSplitEditor::setAccountId(const QString& id)
0302 {
0303     d->ui->accountCombo->clearEditText();
0304     d->ui->accountCombo->setSelected(id);
0305 }
0306 
0307 
0308 QString NewSplitEditor::memo() const
0309 {
0310     return d->ui->memoEdit->toPlainText();
0311 }
0312 
0313 void NewSplitEditor::setMemo(const QString& memo)
0314 {
0315     d->ui->memoEdit->setPlainText(memo);
0316 }
0317 
0318 MyMoneyMoney NewSplitEditor::amount() const
0319 {
0320     return d->amountHelper->value();
0321 }
0322 
0323 void NewSplitEditor::setAmount(MyMoneyMoney value)
0324 {
0325     d->amountHelper->setValue(value);
0326 }
0327 
0328 QString NewSplitEditor::costCenterId() const
0329 {
0330     const int row = d->ui->costCenterCombo->currentIndex();
0331     QModelIndex index = d->ui->costCenterCombo->model()->index(row, 0);
0332     return d->ui->costCenterCombo->model()->data(index, CostCenterModel::CostCenterIdRole).toString();
0333 }
0334 
0335 void NewSplitEditor::setCostCenterId(const QString& id)
0336 {
0337     QModelIndex index = Models::indexById(d->costCenterModel, CostCenterModel::CostCenterIdRole, id);
0338     if(index.isValid()) {
0339         d->ui->costCenterCombo->setCurrentIndex(index.row());
0340     }
0341 }
0342 
0343 QString NewSplitEditor::number() const
0344 {
0345     return d->ui->numberEdit->text();
0346 }
0347 
0348 void NewSplitEditor::setNumber(const QString& number)
0349 {
0350     d->ui->numberEdit->setText(number);
0351 }
0352 
0353 
0354 QString NewSplitEditor::splitId() const
0355 {
0356     return d->transactionSplitId;
0357 }
0358 
0359 void NewSplitEditor::numberChanged(const QString& newNumber)
0360 {
0361     d->numberChanged(newNumber);
0362 }
0363 
0364 void NewSplitEditor::categoryChanged(const QString& accountId)
0365 {
0366     d->categoryChanged(accountId);
0367 }
0368 
0369 void NewSplitEditor::costCenterChanged(int costCenterIndex)
0370 {
0371     d->costCenterChanged(costCenterIndex);
0372 }
0373 
0374 void NewSplitEditor::amountChanged()
0375 {
0376 //  d->amountChanged(d->amountHelper); // useless call as reported by coverity scan
0377 }