File indexing completed on 2024-05-12 16:42:20

0001 /*
0002     SPDX-FileCopyrightText: 2016-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 "splitmodel.h"
0008 
0009 // ----------------------------------------------------------------------------
0010 // QT Includes
0011 
0012 #include <QString>
0013 #include <QDebug>
0014 
0015 // ----------------------------------------------------------------------------
0016 // KDE Includes
0017 
0018 #include <KLocalizedString>
0019 
0020 // ----------------------------------------------------------------------------
0021 // Project Includes
0022 
0023 #include "models.h"
0024 #include "costcentermodel.h"
0025 #include "mymoneysplit.h"
0026 #include "mymoneytransaction.h"
0027 #include "mymoneyfile.h"
0028 #include "mymoneyaccount.h"
0029 #include "mymoneypayee.h"
0030 #include "mymoneymoney.h"
0031 #include "mymoneyexception.h"
0032 #include "kmymoneyutils.h"
0033 #include "modelenums.h"
0034 
0035 using namespace eLedgerModel;
0036 using namespace eMyMoney;
0037 
0038 class SplitModelPrivate
0039 {
0040 public:
0041     SplitModelPrivate()
0042         : m_invertValues(false)
0043     {}
0044 
0045     bool isCreateSplitEntry(const QString& id) const {
0046         return id.isEmpty();
0047     }
0048 
0049     MyMoneyTransaction    m_transaction;
0050     QVector<MyMoneySplit> m_splits;
0051     bool                  m_invertValues;
0052 };
0053 
0054 SplitModel::SplitModel(QObject* parent) :
0055     QAbstractTableModel(parent),
0056     d_ptr(new SplitModelPrivate)
0057 {
0058 }
0059 
0060 SplitModel::~SplitModel()
0061 {
0062 }
0063 
0064 QString SplitModel::newSplitId()
0065 {
0066     return QLatin1String("New-ID");
0067 }
0068 
0069 bool SplitModel::isNewSplitId(const QString& id)
0070 {
0071     return id.compare(newSplitId()) == 0;
0072 }
0073 
0074 
0075 int SplitModel::rowCount(const QModelIndex& parent) const
0076 {
0077     Q_D(const SplitModel);
0078     Q_UNUSED(parent);
0079     return d->m_splits.count();
0080 }
0081 
0082 int SplitModel::columnCount(const QModelIndex& parent) const
0083 {
0084     Q_UNUSED(parent);
0085     return (int)Column::LastColumn;
0086 }
0087 
0088 
0089 void SplitModel::deepCopy(const SplitModel& right, bool revertSplitSign)
0090 {
0091     Q_D(SplitModel);
0092     beginInsertRows(QModelIndex(), 0, right.rowCount());
0093     d->m_splits = right.d_func()->m_splits;
0094     d->m_transaction = right.d_func()->m_transaction;
0095     if(revertSplitSign) {
0096         for(int idx = 0; idx < d->m_splits.count(); ++idx) {
0097             MyMoneySplit& split = d->m_splits[idx];
0098             split.setShares(-split.shares());
0099             split.setValue(-split.value());
0100         }
0101     }
0102     endInsertRows();
0103 }
0104 
0105 QVariant SplitModel::headerData(int section, Qt::Orientation orientation, int role) const
0106 {
0107     if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0108         switch(section) {
0109         case (int)Column::CostCenter:
0110             return i18n("Cost Center");
0111         case (int)Column::Detail:
0112             return i18n("Category");
0113         case (int)Column::Number:
0114             return i18n("No");
0115         case (int)Column::Date:
0116             return i18n("Date");
0117         case (int)Column::Security:
0118             return i18n("Security");
0119         case (int)Column::Reconciliation:
0120             return i18n("C");
0121         case (int)Column::Payment:
0122             return i18n("Payment");
0123         case (int)Column::Deposit:
0124             return i18n("Deposit");
0125         case (int)Column::Quantity:
0126             return i18n("Quantity");
0127         case (int)Column::Price:
0128             return i18n("Price");
0129         case (int)Column::Amount:
0130             return i18n("Amount");
0131         case (int)Column::Value:
0132             return i18n("Value");
0133         case (int)Column::Balance:
0134             return i18n("Balance");
0135         }
0136     }
0137     return QAbstractItemModel::headerData(section, orientation, role);
0138 }
0139 
0140 QVariant SplitModel::data(const QModelIndex& index, int role) const
0141 {
0142     Q_D(const SplitModel);
0143     if(!index.isValid())
0144         return QVariant();
0145     if(index.row() < 0 || index.row() >= d->m_splits.count())
0146         return QVariant();
0147 
0148     QVariant rc;
0149     MyMoneyAccount acc;
0150     MyMoneyMoney value;
0151     const MyMoneySplit& split = d->m_splits[index.row()];
0152     QModelIndex subIndex;
0153     CostCenterModel* ccModel = Models::instance()->costCenterModel();
0154 
0155     switch(role) {
0156     case Qt::DisplayRole:
0157         // make sure to never return any displayable text for the dummy entry
0158         if(!d->isCreateSplitEntry(split.id())) {
0159             switch(index.column()) {
0160             case (int)Column::Detail:
0161                 rc = MyMoneyFile::instance()->accountToCategory(split.accountId());
0162                 break;
0163             case (int)Column::CostCenter:
0164                 subIndex = Models::indexById(ccModel, CostCenterModel::CostCenterIdRole, split.costCenterId());
0165                 rc = ccModel->data(subIndex);
0166                 break;
0167             case (int)Column::Number:
0168                 rc = split.number();
0169                 break;
0170             case (int)Column::Reconciliation:
0171                 rc = KMyMoneyUtils::reconcileStateToString(split.reconcileFlag(), false);
0172                 break;
0173             case (int)Column::Payment:
0174                 if(split.value().isNegative()) {
0175                     acc = MyMoneyFile::instance()->account(split.accountId());
0176                     rc = (-split).value(d->m_transaction.commodity(), acc.currencyId()).formatMoney(acc.fraction());
0177                 }
0178                 break;
0179             case (int)Column::Deposit:
0180                 if(!split.value().isNegative()) {
0181                     acc = MyMoneyFile::instance()->account(split.accountId());
0182                     rc = split.value(d->m_transaction.commodity(), acc.currencyId()).formatMoney(acc.fraction());
0183                 }
0184                 break;
0185             default:
0186                 break;
0187             }
0188         }
0189         break;
0190 
0191     case Qt::TextAlignmentRole:
0192         switch(index.column()) {
0193         case (int)Column::Payment:
0194         case (int)Column::Deposit:
0195         case (int)Column::Amount:
0196         case (int)Column::Balance:
0197         case (int)Column::Value:
0198             rc = QVariant(Qt::AlignRight| Qt::AlignTop);
0199             break;
0200         case (int)Column::Reconciliation:
0201             rc = QVariant(Qt::AlignHCenter | Qt::AlignTop);
0202             break;
0203         default:
0204             rc = QVariant(Qt::AlignLeft | Qt::AlignTop);
0205             break;
0206         }
0207         break;
0208 
0209     case (int)Role::AccountId:
0210         rc = split.accountId();
0211         break;
0212 
0213     case (int)Role::Account:
0214         rc = MyMoneyFile::instance()->accountToCategory(split.accountId());
0215         break;
0216 
0217     case (int)Role::TransactionId:
0218         rc = QString("%1").arg(d->m_transaction.id());
0219         break;
0220 
0221     case (int)Role::TransactionSplitId:
0222         rc = QString("%1-%2").arg(d->m_transaction.id(), split.id());
0223         break;
0224 
0225     case (int)Role::SplitId:
0226         rc = split.id();
0227         break;
0228 
0229     case (int)Role::Memo:
0230     case (int)Role::SingleLineMemo:
0231         rc = split.memo();
0232         if(role == (int)Role::SingleLineMemo) {
0233             QString txt = rc.toString();
0234             // remove empty lines
0235             txt.replace("\n\n", "\n");
0236             // replace '\n' with ", "
0237             txt.replace('\n', ", ");
0238             rc = txt;
0239         }
0240         break;
0241 
0242     case (int)Role::SplitShares:
0243         rc = QVariant::fromValue<MyMoneyMoney>(split.shares());
0244         break;
0245 
0246     case (int)Role::SplitValue:
0247         acc = MyMoneyFile::instance()->account(split.accountId());
0248         rc = QVariant::fromValue<MyMoneyMoney>(split.value(d->m_transaction.commodity(), acc.currencyId()));
0249         break;
0250 
0251     case (int)Role::PayeeName:
0252         try {
0253             rc = MyMoneyFile::instance()->payee(split.payeeId()).name();
0254         } catch (const MyMoneyException &e) {
0255         }
0256         break;
0257 
0258     case (int)Role::CostCenterId:
0259         rc = split.costCenterId();
0260         break;
0261 
0262     case (int)Role::TransactionCommodity:
0263         return d->m_transaction.commodity();
0264         break;
0265 
0266     case (int)Role::Number:
0267         rc = split.number();
0268         break;
0269 
0270     case (int)Role::PayeeId:
0271         rc = split.payeeId();
0272         break;
0273 
0274     default:
0275         if(role >= Qt::UserRole) {
0276             qWarning() << "Undefined role" << role << "(" << role-Qt::UserRole << ") in SplitModel::data";
0277         }
0278         break;
0279     }
0280     return rc;
0281 }
0282 
0283 bool SplitModel::setData(const QModelIndex& index, const QVariant& value, int role)
0284 {
0285     Q_D(SplitModel);
0286     bool rc = false;
0287     if(index.isValid()) {
0288         MyMoneySplit& split = d->m_splits[index.row()];
0289         if(split.id().isEmpty()) {
0290             split = MyMoneySplit(newSplitId(), split);
0291         }
0292         QString val;
0293         rc = true;
0294         switch(role) {
0295         case (int)Role::PayeeId:
0296             split.setPayeeId(value.toString());
0297             break;
0298 
0299         case (int)Role::AccountId:
0300             split.setAccountId(value.toString());
0301             break;
0302 
0303         case (int)Role::Memo:
0304             split.setMemo(value.toString());
0305             break;
0306 
0307         case (int)Role::CostCenterId:
0308             val = value.toString();
0309             split.setCostCenterId(value.toString());
0310             break;
0311 
0312         case (int)Role::Number:
0313             split.setNumber(value.toString());
0314             break;
0315 
0316         case (int)Role::SplitShares:
0317             split.setShares(value.value<MyMoneyMoney>());
0318             break;
0319 
0320         case (int)Role::SplitValue:
0321             split.setValue(value.value<MyMoneyMoney>());
0322             break;
0323 
0324         case (int)Role::EmitDataChanged:
0325         {
0326             // the whole row changed
0327             QModelIndex topLeft = this->index(index.row(), 0);
0328             QModelIndex bottomRight = this->index(index.row(), this->columnCount()-1);
0329             emit dataChanged(topLeft, bottomRight);
0330         }
0331         break;
0332 
0333         default:
0334             rc = false;
0335             break;
0336         }
0337     }
0338 
0339     return rc;
0340 }
0341 
0342 
0343 void SplitModel::addSplit(const QString& transactionSplitId)
0344 {
0345     Q_D(SplitModel);
0346     QRegExp transactionSplitIdExp("^(\\w+)-(\\w+)$");
0347     if(transactionSplitIdExp.exactMatch(transactionSplitId)) {
0348         const QString transactionId = transactionSplitIdExp.cap(1);
0349         const QString splitId = transactionSplitIdExp.cap(2);
0350         if(transactionId != d->m_transaction.id()) {
0351             try {
0352                 d->m_transaction = MyMoneyFile::instance()->transaction(transactionId);
0353             } catch (const MyMoneyException &e) {
0354                 d->m_transaction = MyMoneyTransaction();
0355             }
0356         }
0357         try {
0358             beginInsertRows(QModelIndex(), rowCount(), rowCount());
0359             d->m_splits.append(d->m_transaction.splitById(splitId));
0360             endInsertRows();
0361         } catch (const MyMoneyException &e) {
0362             d->m_transaction = MyMoneyTransaction();
0363         }
0364     }
0365 }
0366 
0367 void SplitModel::addEmptySplitEntry()
0368 {
0369     Q_D(SplitModel);
0370     QModelIndexList list = match(index(0, 0), (int)Role::SplitId, QString(), -1, Qt::MatchExactly);
0371     if(list.count() == 0) {
0372         beginInsertRows(QModelIndex(), rowCount(), rowCount());
0373         // d->m_splits.append(MyMoneySplit(d->newSplitEntryId(), MyMoneySplit()));
0374         d->m_splits.append(MyMoneySplit());
0375         endInsertRows();
0376     }
0377 }
0378 
0379 void SplitModel::removeEmptySplitEntry()
0380 {
0381     Q_D(SplitModel);
0382     // QModelIndexList list = match(index(0, 0), SplitIdRole, d->newSplitEntryId(), -1, Qt::MatchExactly);
0383     QModelIndexList list = match(index(0, 0), (int)Role::SplitId, QString(), -1, Qt::MatchExactly);
0384     if(list.count()) {
0385         QModelIndex index = list.at(0);
0386         beginRemoveRows(QModelIndex(), index.row(), index.row());
0387         d->m_splits.remove(index.row(), 1);
0388         endRemoveRows();
0389     }
0390 }
0391 
0392 bool SplitModel::removeRows(int row, int count, const QModelIndex& parent)
0393 {
0394     Q_D(SplitModel);
0395     bool rc = false;
0396     if(count > 0) {
0397         beginRemoveRows(parent, row, row + count - 1);
0398         d->m_splits.remove(row, count);
0399         endRemoveRows();
0400         rc = true;
0401     }
0402     return rc;
0403 }
0404 
0405 Qt::ItemFlags SplitModel::flags(const QModelIndex& index) const
0406 {
0407     Q_D(const SplitModel);
0408     Qt::ItemFlags flags;
0409 
0410     if(!index.isValid())
0411         return flags;
0412     if(index.row() < 0 || index.row() >= d->m_splits.count())
0413         return flags;
0414 
0415     return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
0416 }
0417 
0418 
0419 #if 0
0420 void SplitModel::removeSplit(const LedgerTransaction& t)
0421 {
0422     Q_D(SplitModel);
0423     QModelIndexList list = match(index(0, 0), TransactionSplitIdRole, t.transactionSplitId(), -1, Qt::MatchExactly);
0424     if(list.count()) {
0425         QModelIndex index = list.at(0);
0426         beginRemoveRows(QModelIndex(), index.row(), index.row());
0427         delete d->m_ledgerItems[index.row()];
0428         d->m_ledgerItems.remove(index.row(), 1);
0429         endRemoveRows();
0430 
0431         // just make sure we're in sync
0432         Q_ASSERT(d->m_ledgerItems.count() == rowCount());
0433     }
0434 }
0435 #endif