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

0001 /*
0002     SPDX-FileCopyrightText: 2019 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "splitmodel.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QStandardItem>
0012 
0013 // ----------------------------------------------------------------------------
0014 // KDE Includes
0015 
0016 #include <KLocalizedString>
0017 
0018 // ----------------------------------------------------------------------------
0019 // Project Includes
0020 
0021 #include "mymoneyenums.h"
0022 #include "mymoneyfile.h"
0023 #include "accountsmodel.h"
0024 #include "mymoneymoney.h"
0025 #include "mymoneysecurity.h"
0026 #include "mymoneyexception.h"
0027 
0028 struct SplitModel::Private
0029 {
0030     Private(SplitModel* qq)
0031         : q(qq)
0032         , currentSplitCount(-1)
0033         , headerData(QHash<Column, QString> ({
0034         { Category, i18nc("Split header", "Category") },
0035         { Memo, i18nc("Split header", "Memo") },
0036         { Payment, i18nc("Split header", "Payment") },
0037         { Deposit, i18nc("Split header", "Deposit") },
0038     }))
0039     {
0040     }
0041 
0042     void copyFrom(const SplitModel& right)
0043     {
0044         q->unload();
0045         headerData = right.d->headerData;
0046         const auto rows = right.rowCount();
0047         for (int row = 0; row < rows; ++row) {
0048             const auto idx = right.index(row, 0);
0049             const auto split = right.itemByIndex(idx);
0050             q->appendSplit(split);
0051         }
0052         updateItemCount();
0053     }
0054 
0055     int splitCount() const
0056     {
0057         int count = 0;
0058         const auto rows = q->rowCount();
0059         for (auto row = 0; row < rows; ++row) {
0060             const auto idx = q->index(row, 0);
0061             if (!idx.data(eMyMoney::Model::SplitAccountIdRole).toString().isEmpty()) {
0062                 ++count;
0063             }
0064         }
0065         return count;
0066     }
0067 
0068     void updateItemCount()
0069     {
0070         const auto count = splitCount();
0071         if (count != currentSplitCount) {
0072             currentSplitCount = count;
0073             emit q->itemCountChanged(currentSplitCount);
0074         }
0075     }
0076 
0077 
0078     QString counterAccount() const
0079     {
0080         // A transaction can have more than 2 splits ...
0081         if(splitCount() > 1) {
0082             return i18n("Split transaction");
0083 
0084             // ... exactly two splits ...
0085         } else if(splitCount() == 1) {
0086             // we have to check which one is filled and which one
0087             // could be an empty (new) split
0088             const auto rows = q->rowCount();
0089             for (auto row = 0; row < rows; ++row) {
0090                 const auto idx = q->index(row, 0);
0091                 const auto accountId = idx.data(eMyMoney::Model::SplitAccountIdRole).toString();
0092                 if (!accountId.isEmpty()) {
0093                     return MyMoneyFile::instance()->accountsModel()->accountIdToHierarchicalName(accountId);
0094                 }
0095             }
0096 
0097             // ... or a single split
0098 #if 0
0099         } else if(!idx.data(eMyMoney::Model::SplitSharesRole).value<MyMoneyMoney>().isZero()) {
0100             return i18n("*** UNASSIGNED ***");
0101 #endif
0102         }
0103         return QString();
0104     }
0105 
0106     int currencyPrecision(const MyMoneySplit& split) const
0107     {
0108         const auto account = MyMoneyFile::instance()->accountsModel()->itemById(split.accountId());
0109         if (!account.id().isEmpty()) {
0110             try {
0111                 const auto currency = MyMoneyFile::instance()->currency(account.currencyId());
0112                 if (Q_UNLIKELY(account.accountType() == eMyMoney::Account::Type::Cash)) {
0113                     return currency.smallestCashFraction();
0114                 }
0115                 return currency.smallestAccountFraction();
0116             } catch(MyMoneyException& e) {
0117             }
0118         }
0119         return 100;
0120     }
0121 
0122     SplitModel*   q;
0123     int                       currentSplitCount;
0124     QHash<Column, QString>    headerData;
0125 };
0126 
0127 SplitModel::SplitModel(QObject* parent, QUndoStack* undoStack)
0128     : MyMoneyModel<MyMoneySplit>(parent, QStringLiteral("S"), 4, undoStack)
0129     , d(new Private(this))
0130 {
0131     // new splits in the split model start with 2 instead of 1
0132     // since the first split id is assigned by the transaction
0133     // editor when the transaction is created. (see
0134     // NewTransactionEditor::saveTransaction() )
0135     ++m_nextId;
0136     connect(this, &SplitModel::modelReset, this, [&] { d->updateItemCount(); });
0137 }
0138 
0139 SplitModel::SplitModel(QObject* parent, QUndoStack* undoStack, const SplitModel& right)
0140     : MyMoneyModel<MyMoneySplit>(parent, QStringLiteral("S"), 4, undoStack)
0141     , d(new Private(this))
0142 {
0143     d->copyFrom(right);
0144 }
0145 
0146 SplitModel& SplitModel::operator=(const SplitModel& right)
0147 {
0148     d->copyFrom(right);
0149     return *this;
0150 }
0151 
0152 SplitModel::~SplitModel()
0153 {
0154 }
0155 
0156 int SplitModel::columnCount(const QModelIndex& parent) const
0157 {
0158     Q_UNUSED(parent);
0159     Q_ASSERT(d->headerData.count() == MaxColumns);
0160     return MaxColumns;
0161 }
0162 
0163 QString SplitModel::newSplitId()
0164 {
0165     return QStringLiteral("New-ID");
0166 }
0167 
0168 bool SplitModel::isNewSplitId(const QString& id)
0169 {
0170     return id.compare(newSplitId()) == 0;
0171 }
0172 
0173 QVariant SplitModel::headerData(int section, Qt::Orientation orientation, int role) const
0174 {
0175     if (orientation == Qt::Horizontal) {
0176         switch (role) {
0177         case Qt::DisplayRole:
0178             return d->headerData.value(static_cast<Column>(section));
0179 
0180         case Qt::SizeHintRole:
0181             return QSize(20, 20);
0182         }
0183         return {};
0184     }
0185     if (orientation == Qt::Vertical && role == Qt::SizeHintRole) {
0186         return QSize(10, 10);
0187     }
0188 
0189     return QAbstractItemModel::headerData(section, orientation, role);
0190 }
0191 
0192 Qt::ItemFlags SplitModel::flags(const QModelIndex& index) const
0193 {
0194     Q_UNUSED(index);
0195     return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
0196 }
0197 
0198 QVariant SplitModel::data(const QModelIndex& idx, int role) const
0199 {
0200     if (!idx.isValid())
0201         return QVariant();
0202     if (idx.row() < 0 || idx.row() >= rowCount(idx.parent()))
0203         return QVariant();
0204 
0205     const MyMoneySplit& split = static_cast<TreeItem<MyMoneySplit>*>(idx.internalPointer())->constDataRef();
0206     switch(role) {
0207     case Qt::DisplayRole:
0208     case Qt::EditRole:
0209         switch(idx.column()) {
0210         case Column::Category:
0211             return MyMoneyFile::instance()->accountsModel()->itemById(split.accountId()).name();
0212 
0213         case Column::Memo:
0214         {
0215             QString rc(split.memo());
0216             // remove empty lines
0217             rc.replace("\n\n", "\n");
0218             // replace '\n' with ", "
0219             rc.replace('\n', ", ");
0220             return rc;
0221         }
0222 
0223         case Column::Payment:
0224             if (!split.id().isEmpty()) {
0225                 const auto value = split.value();
0226                 if (value.isPositive()) {
0227                     return value.formatMoney(d->currencyPrecision(split));
0228                 }
0229             }
0230             return {};
0231 
0232         case Column::Deposit:
0233             if (!split.id().isEmpty()) {
0234                 const auto value = split.value();
0235                 if (value.isNegative() || value.isZero()) {
0236                     return (-value).formatMoney(d->currencyPrecision(split));
0237                 }
0238             }
0239             return {};
0240 
0241         default:
0242             break;
0243         }
0244         break;
0245 
0246     case Qt::TextAlignmentRole:
0247         switch (idx.column()) {
0248         case Payment:
0249         case Deposit:
0250             return QVariant(Qt::AlignRight | Qt::AlignTop);
0251 
0252         default:
0253             break;
0254         }
0255         return QVariant(Qt::AlignLeft | Qt::AlignTop);
0256 
0257     case eMyMoney::Model::IdRole:
0258         return split.id();
0259 
0260     case eMyMoney::Model::SplitMemoRole:
0261         return split.memo();
0262 
0263     case eMyMoney::Model::SplitAccountIdRole:
0264         return split.accountId();
0265 
0266     case eMyMoney::Model::SplitSharesRole:
0267         return QVariant::fromValue<MyMoneyMoney>(split.shares());
0268 
0269     case eMyMoney::Model::SplitValueRole:
0270         return QVariant::fromValue<MyMoneyMoney>(split.value());
0271 
0272     case eMyMoney::Model::SplitCostCenterIdRole:
0273         return split.costCenterId();
0274 
0275     case eMyMoney::Model::SplitNumberRole:
0276         return split.number();
0277 
0278     case eMyMoney::Model::SplitPayeeIdRole:
0279         return split.payeeId();
0280 
0281     case eMyMoney::Model::TransactionCounterAccountRole:
0282         break;
0283 
0284     default:
0285         break;
0286     }
0287     return {};
0288 }
0289 
0290 bool SplitModel::setData(const QModelIndex& idx, const QVariant& value, int role)
0291 {
0292     if(!idx.isValid()) {
0293         return false;
0294     }
0295     if (idx.row() < 0 || idx.row() >= rowCount(idx.parent())) {
0296         return false;
0297     }
0298 
0299     const auto startIdx = idx.model()->index(idx.row(), 0);
0300     const auto endIdx = idx.model()->index(idx.row(), idx.model()->columnCount()-1);
0301     MyMoneySplit& split = static_cast<TreeItem<MyMoneySplit>*>(idx.internalPointer())->dataRef();
0302 
0303     // in case we modify the data of a new split, we need to setup an id
0304     // this will be updated once we add the split to the transaction
0305     if (split.id().isEmpty()) {
0306         split = MyMoneySplit(newSplitId(), split);
0307     }
0308 
0309     switch(role) {
0310     case Qt::DisplayRole:
0311     case Qt::EditRole:
0312         break;
0313 
0314     case eMyMoney::Model::SplitNumberRole:
0315         split.setNumber(value.toString());
0316         emit dataChanged(startIdx, endIdx);
0317         return true;
0318 
0319     case eMyMoney::Model::SplitMemoRole:
0320         split.setMemo(value.toString());
0321         emit dataChanged(startIdx, endIdx);
0322         return true;
0323 
0324     case eMyMoney::Model::SplitAccountIdRole:
0325         split.setAccountId(value.toString());
0326         emit dataChanged(startIdx, endIdx);
0327         return true;
0328 
0329     case eMyMoney::Model::SplitCostCenterIdRole:
0330         split.setCostCenterId(value.toString());
0331         emit dataChanged(startIdx, endIdx);
0332         return true;
0333 
0334     case eMyMoney::Model::SplitSharesRole:
0335         split.setShares(value.value<MyMoneyMoney>());
0336         emit dataChanged(startIdx, endIdx);
0337         return true;
0338 
0339     case eMyMoney::Model::SplitValueRole:
0340         split.setValue(value.value<MyMoneyMoney>());
0341         emit dataChanged(startIdx, endIdx);
0342         return true;
0343 
0344     case eMyMoney::Model::SplitPayeeIdRole:
0345         split.setPayeeId(value.toString());
0346         emit dataChanged(startIdx, endIdx);
0347         return true;
0348 
0349     }
0350     return QAbstractItemModel::setData(idx, value, role);
0351 }
0352 
0353 void SplitModel::appendSplit(const MyMoneySplit& split)
0354 {
0355     doAddItem(split);
0356 }
0357 
0358 void SplitModel::appendEmptySplit()
0359 {
0360     const QModelIndexList list = match(index(0, 0), eMyMoney::Model::IdRole, QString(), -1, Qt::MatchExactly);
0361     if(list.isEmpty()) {
0362         doAddItem(MyMoneySplit());
0363     }
0364 }
0365 
0366 void SplitModel::removeEmptySplit()
0367 {
0368     const QModelIndexList list = match(index(0, 0), eMyMoney::Model::IdRole, QString(), -1, Qt::MatchExactly);
0369     if(!list.isEmpty()) {
0370         removeRow(list.first().row(), list.first().parent());
0371     }
0372 }
0373 
0374 void SplitModel::doAddItem(const MyMoneySplit& item, const QModelIndex& parentIdx)
0375 {
0376     MyMoneyModel::doAddItem(item, parentIdx);
0377     d->updateItemCount();
0378 }
0379 
0380 void SplitModel::doRemoveItem(const MyMoneySplit& before)
0381 {
0382     MyMoneyModel::doRemoveItem(before);
0383     d->updateItemCount();
0384 }
0385 
0386 MyMoneyMoney SplitModel::valueSum() const
0387 {
0388     MyMoneyMoney sum;
0389     const auto rows = rowCount();
0390     for (int row = 0; row < rows; ++row) {
0391         const auto idx = index(row, 0);
0392         sum += idx.data(eMyMoney::Model::SplitValueRole).value<MyMoneyMoney>();
0393     }
0394     return sum;
0395 }