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 }