1 /*
2 * Copyright 2000-2002 Michael Edwardes <mte@users.sourceforge.net>
3 * Copyright 2001 Felix Rodriguez <frodriguez@users.sourceforge.net>
4 * Copyright 2002-2003 Kevin Tambascio <ktambascio@users.sourceforge.net>
5 * Copyright 2006-2017 Thomas Baumgart <tbaumgart@kde.org>
6 * Copyright 2006 Ace Jones <acejones@users.sourceforge.net>
7 * Copyright 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of
12 * the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "mymoneyaccount.h"
24 #include "mymoneyaccount_p.h"
25
26 // ----------------------------------------------------------------------------
27 // QT Includes
28
29 #include <QRegExp>
30 #include <QPixmap>
31 #include <QPixmapCache>
32 #include <QPainter>
33 #include <QIcon>
34 #include <QDebug>
35
36 // ----------------------------------------------------------------------------
37 // KDE Includes
38
39 #include <KLocalizedString>
40
41 // ----------------------------------------------------------------------------
42 // Project Includes
43
44 #include "mymoneyutils.h"
45 #include "mymoneyexception.h"
46 #include "mymoneysplit.h"
47 #include "mymoneyfile.h"
48 #include "mymoneysecurity.h"
49 #include "mymoneyinstitution.h"
50 #include "mymoneypayee.h"
51 #include "payeeidentifier/payeeidentifiertyped.h"
52 #include "payeeidentifier/ibanbic/ibanbic.h"
53 #include "payeeidentifier/nationalaccount/nationalaccount.h"
54 #include "icons/icons.h"
55
56 using namespace Icons;
57
MyMoneyAccount()58 MyMoneyAccount::MyMoneyAccount() :
59 MyMoneyObject(*new MyMoneyAccountPrivate),
60 MyMoneyKeyValueContainer()
61 {
62 }
63
MyMoneyAccount(const QString & id)64 MyMoneyAccount::MyMoneyAccount(const QString &id):
65 MyMoneyObject(*new MyMoneyAccountPrivate, id),
66 MyMoneyKeyValueContainer()
67 {
68 }
69
MyMoneyAccount(const MyMoneyAccount & other)70 MyMoneyAccount::MyMoneyAccount(const MyMoneyAccount& other) :
71 MyMoneyObject(*new MyMoneyAccountPrivate(*other.d_func()), other.id()),
72 MyMoneyKeyValueContainer(other)
73 {
74 }
75
MyMoneyAccount(const QString & id,const MyMoneyAccount & other)76 MyMoneyAccount::MyMoneyAccount(const QString& id, const MyMoneyAccount& other) :
77 MyMoneyObject(*new MyMoneyAccountPrivate(*other.d_func()), id),
78 MyMoneyKeyValueContainer(other)
79 {
80 }
81
~MyMoneyAccount()82 MyMoneyAccount::~MyMoneyAccount()
83 {
84 }
85
touch()86 void MyMoneyAccount::touch()
87 {
88 setLastModified(QDate::currentDate());
89 }
90
accountType() const91 eMyMoney::Account::Type MyMoneyAccount::accountType() const
92 {
93 Q_D(const MyMoneyAccount);
94 return d->m_accountType;
95 }
96
setAccountType(const Account::Type type)97 void MyMoneyAccount::setAccountType(const Account::Type type)
98 {
99 Q_D(MyMoneyAccount);
100 d->m_accountType = type;
101 }
102
institutionId() const103 QString MyMoneyAccount::institutionId() const
104 {
105 Q_D(const MyMoneyAccount);
106 return d->m_institution;
107 }
108
setInstitutionId(const QString & id)109 void MyMoneyAccount::setInstitutionId(const QString& id)
110 {
111 Q_D(MyMoneyAccount);
112 d->m_institution = id;
113 }
114
name() const115 QString MyMoneyAccount::name() const
116 {
117 Q_D(const MyMoneyAccount);
118 return d->m_name;
119 }
120
setName(const QString & name)121 void MyMoneyAccount::setName(const QString& name)
122 {
123 Q_D(MyMoneyAccount);
124 d->m_name = name;
125 }
126
number() const127 QString MyMoneyAccount::number() const
128 {
129 Q_D(const MyMoneyAccount);
130 return d->m_number;
131 }
132
setNumber(const QString & number)133 void MyMoneyAccount::setNumber(const QString& number)
134 {
135 Q_D(MyMoneyAccount);
136 d->m_number = number;
137 }
138
description() const139 QString MyMoneyAccount::description() const
140 {
141 Q_D(const MyMoneyAccount);
142 return d->m_description;
143 }
144
setDescription(const QString & desc)145 void MyMoneyAccount::setDescription(const QString& desc)
146 {
147 Q_D(MyMoneyAccount);
148 d->m_description = desc;
149 }
150
openingDate() const151 QDate MyMoneyAccount::openingDate() const
152 {
153 Q_D(const MyMoneyAccount);
154 return d->m_openingDate;
155 }
156
setOpeningDate(const QDate & date)157 void MyMoneyAccount::setOpeningDate(const QDate& date)
158 {
159 Q_D(MyMoneyAccount);
160 d->m_openingDate = date;
161 }
162
lastReconciliationDate() const163 QDate MyMoneyAccount::lastReconciliationDate() const
164 {
165 Q_D(const MyMoneyAccount);
166 return d->m_lastReconciliationDate;
167 }
168
setLastReconciliationDate(const QDate & date)169 void MyMoneyAccount::setLastReconciliationDate(const QDate& date)
170 {
171 Q_D(MyMoneyAccount);
172 d->m_lastReconciliationDate = date;
173 }
174
lastModified() const175 QDate MyMoneyAccount::lastModified() const
176 {
177 Q_D(const MyMoneyAccount);
178 return d->m_lastModified;
179 }
180
setLastModified(const QDate & date)181 void MyMoneyAccount::setLastModified(const QDate& date)
182 {
183 Q_D(MyMoneyAccount);
184 d->m_lastModified = date;
185 }
186
parentAccountId() const187 QString MyMoneyAccount::parentAccountId() const
188 {
189 Q_D(const MyMoneyAccount);
190 return d->m_parentAccount;
191 }
192
setParentAccountId(const QString & parent)193 void MyMoneyAccount::setParentAccountId(const QString& parent)
194 {
195 Q_D(MyMoneyAccount);
196 d->m_parentAccount = parent;
197 }
198
accountList() const199 QStringList MyMoneyAccount::accountList() const
200 {
201 Q_D(const MyMoneyAccount);
202 return d->m_accountList;
203 }
204
accountCount() const205 int MyMoneyAccount::accountCount() const
206 {
207 Q_D(const MyMoneyAccount);
208 return d->m_accountList.count();
209 }
210
addAccountId(const QString & account)211 void MyMoneyAccount::addAccountId(const QString& account)
212 {
213 Q_D(MyMoneyAccount);
214 if (!d->m_accountList.contains(account))
215 d->m_accountList += account;
216 }
217
removeAccountIds()218 void MyMoneyAccount::removeAccountIds()
219 {
220 Q_D(MyMoneyAccount);
221 d->m_accountList.clear();
222 }
223
removeAccountId(const QString & account)224 void MyMoneyAccount::removeAccountId(const QString& account)
225 {
226 Q_D(MyMoneyAccount);
227 const auto pos = d->m_accountList.indexOf(account);
228 if (pos != -1)
229 d->m_accountList.removeAt(pos);
230 }
231
operator ==(const MyMoneyAccount & right) const232 bool MyMoneyAccount::operator == (const MyMoneyAccount& right) const
233 {
234 Q_D(const MyMoneyAccount);
235 auto d2 = static_cast<const MyMoneyAccountPrivate *>(right.d_func());
236 return (MyMoneyKeyValueContainer::operator==(right) &&
237 MyMoneyObject::operator==(right) &&
238 (d->m_accountList == d2->m_accountList) &&
239 (d->m_accountType == d2->m_accountType) &&
240 (d->m_lastModified == d2->m_lastModified) &&
241 (d->m_lastReconciliationDate == d2->m_lastReconciliationDate) &&
242 ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name)) &&
243 ((d->m_number.length() == 0 && d2->m_number.length() == 0) || (d->m_number == d2->m_number)) &&
244 ((d->m_description.length() == 0 && d2->m_description.length() == 0) || (d->m_description == d2->m_description)) &&
245 (d->m_openingDate == d2->m_openingDate) &&
246 (d->m_parentAccount == d2->m_parentAccount) &&
247 (d->m_currencyId == d2->m_currencyId) &&
248 (d->m_institution == d2->m_institution));
249 }
250
accountGroup() const251 Account::Type MyMoneyAccount::accountGroup() const
252 {
253 Q_D(const MyMoneyAccount);
254 switch (d->m_accountType) {
255 case Account::Type::Checkings:
256 case Account::Type::Savings:
257 case Account::Type::Cash:
258 case Account::Type::Currency:
259 case Account::Type::Investment:
260 case Account::Type::MoneyMarket:
261 case Account::Type::CertificateDep:
262 case Account::Type::AssetLoan:
263 case Account::Type::Stock:
264 return Account::Type::Asset;
265
266 case Account::Type::CreditCard:
267 case Account::Type::Loan:
268 return Account::Type::Liability;
269
270 default:
271 return d->m_accountType;
272 }
273 }
274
currencyId() const275 QString MyMoneyAccount::currencyId() const
276 {
277 Q_D(const MyMoneyAccount);
278 return d->m_currencyId;
279 }
280
tradingCurrencyId() const281 QString MyMoneyAccount::tradingCurrencyId() const
282 {
283 const auto file = MyMoneyFile::instance();
284
285 // First, get the trading currency (formerly deep currency)
286 auto deepcurrency = file->security(currencyId());
287 if (!deepcurrency.isCurrency())
288 deepcurrency = file->security(deepcurrency.tradingCurrency());
289
290 // Return the trading currency's ID
291 return deepcurrency.id();
292 }
293
isForeignCurrency() const294 bool MyMoneyAccount::isForeignCurrency() const
295 {
296 return (tradingCurrencyId() != MyMoneyFile::instance()->baseCurrency().id());
297 }
298
setCurrencyId(const QString & id)299 void MyMoneyAccount::setCurrencyId(const QString& id)
300 {
301 Q_D(MyMoneyAccount);
302 d->m_currencyId = id;
303 }
304
isAssetLiability() const305 bool MyMoneyAccount::isAssetLiability() const
306 {
307 return accountGroup() == Account::Type::Asset || accountGroup() == Account::Type::Liability;
308 }
309
isIncomeExpense() const310 bool MyMoneyAccount::isIncomeExpense() const
311 {
312 return accountGroup() == Account::Type::Income || accountGroup() == Account::Type::Expense;
313 }
314
isLoan() const315 bool MyMoneyAccount::isLoan() const
316 {
317 return accountType() == Account::Type::Loan || accountType() == Account::Type::AssetLoan;
318 }
319
isInvest() const320 bool MyMoneyAccount::isInvest() const
321 {
322 return accountType() == Account::Type::Stock;
323 }
324
isLiquidAsset() const325 bool MyMoneyAccount::isLiquidAsset() const
326 {
327 return accountType() == Account::Type::Checkings ||
328 accountType() == Account::Type::Savings ||
329 accountType() == Account::Type::Cash;
330 }
331
isLiquidLiability() const332 bool MyMoneyAccount::isLiquidLiability() const
333 {
334 return accountType() == Account::Type::CreditCard;
335 }
336
isCostCenterRequired() const337 bool MyMoneyAccount::isCostCenterRequired() const
338 {
339 return value("CostCenter").toLower() == QLatin1String("yes");
340 }
341
setCostCenterRequired(bool required)342 void MyMoneyAccount::setCostCenterRequired(bool required)
343 {
344 if(required) {
345 setValue("CostCenter", "yes");
346 } else {
347 deletePair("CostCenter");
348 }
349 }
350
hasReferenceTo(const QString & id) const351 bool MyMoneyAccount::hasReferenceTo(const QString& id) const
352 {
353 Q_D(const MyMoneyAccount);
354 return (id == d->m_institution) || (id == d->m_parentAccount) || (id == d->m_currencyId);
355 }
356
setOnlineBankingSettings(const MyMoneyKeyValueContainer & values)357 void MyMoneyAccount::setOnlineBankingSettings(const MyMoneyKeyValueContainer& values)
358 {
359 Q_D(MyMoneyAccount);
360 d->m_onlineBankingSettings = values;
361 }
362
onlineBankingSettings() const363 MyMoneyKeyValueContainer MyMoneyAccount::onlineBankingSettings() const
364 {
365 Q_D(const MyMoneyAccount);
366 return d->m_onlineBankingSettings;
367 }
368
setClosed(bool closed)369 void MyMoneyAccount::setClosed(bool closed)
370 {
371 if (closed)
372 setValue("mm-closed", "yes");
373 else
374 deletePair("mm-closed");
375 }
376
isClosed() const377 bool MyMoneyAccount::isClosed() const
378 {
379 return !(value("mm-closed").isEmpty());
380 }
381
fraction(const MyMoneySecurity & sec) const382 int MyMoneyAccount::fraction(const MyMoneySecurity& sec) const
383 {
384 Q_D(const MyMoneyAccount);
385 int fraction;
386 if (d->m_accountType == Account::Type::Cash)
387 fraction = sec.smallestCashFraction();
388 else
389 fraction = sec.smallestAccountFraction();
390 return fraction;
391 }
392
fraction(const MyMoneySecurity & sec)393 int MyMoneyAccount::fraction(const MyMoneySecurity& sec)
394 {
395 Q_D(MyMoneyAccount);
396 if (d->m_accountType == Account::Type::Cash)
397 d->m_fraction = sec.smallestCashFraction();
398 else
399 d->m_fraction = sec.smallestAccountFraction();
400 return d->m_fraction;
401 }
402
fraction() const403 int MyMoneyAccount::fraction() const
404 {
405 Q_D(const MyMoneyAccount);
406 return d->m_fraction;
407 }
408
isCategory() const409 bool MyMoneyAccount::isCategory() const
410 {
411 Q_D(const MyMoneyAccount);
412 return d->m_accountType == Account::Type::Income || d->m_accountType == Account::Type::Expense;
413 }
414
brokerageName() const415 QString MyMoneyAccount::brokerageName() const
416 {
417 Q_D(const MyMoneyAccount);
418 if (d->m_accountType == Account::Type::Investment)
419 return QString("%1 (%2)").arg(d->m_name, i18nc("Brokerage (suffix for account names)", "Brokerage"));
420 return d->m_name;
421 }
422
balance() const423 MyMoneyMoney MyMoneyAccount::balance() const
424 {
425 Q_D(const MyMoneyAccount);
426 return d->m_balance;
427 }
428
adjustBalance(const MyMoneySplit & s,bool reverse)429 void MyMoneyAccount::adjustBalance(const MyMoneySplit& s, bool reverse)
430 {
431 Q_D(MyMoneyAccount);
432 if (s.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) {
433 if (reverse)
434 d->m_balance = d->m_balance / s.shares();
435 else
436 d->m_balance = d->m_balance * s.shares();
437 } else {
438 if (reverse)
439 d->m_balance -= s.shares();
440 else
441 d->m_balance += s.shares();
442 }
443
444 }
445
setBalance(const MyMoneyMoney & val)446 void MyMoneyAccount::setBalance(const MyMoneyMoney& val)
447 {
448 Q_D(MyMoneyAccount);
449 d->m_balance = val;
450 }
451
accountPixmap(const bool reconcileFlag,const int size) const452 QPixmap MyMoneyAccount::accountPixmap(const bool reconcileFlag, const int size) const
453 {
454 static const QHash<Account::Type, Icon> accToIco {
455 {Account::Type::Asset, Icon::Asset},
456 {Account::Type::Investment, Icon::Stock},
457 {Account::Type::Stock, Icon::Stock},
458 {Account::Type::MoneyMarket, Icon::Stock},
459 {Account::Type::Checkings, Icon::Checking},
460 {Account::Type::Savings, Icon::Savings},
461 {Account::Type::AssetLoan, Icon::LoanAsset},
462 {Account::Type::Loan, Icon::Loan},
463 {Account::Type::CreditCard, Icon::CreditCard},
464 {Account::Type::Asset, Icon::Asset},
465 {Account::Type::Cash, Icon::Cash},
466 {Account::Type::Income, Icon::Income},
467 {Account::Type::Expense, Icon::Expense},
468 {Account::Type::Equity, Icon::Equity}
469 };
470
471 Icon ixIcon = accToIco.value(accountType(), Icon::Liability);
472
473 QString kyIcon = accountTypeToString(accountType()) + QString::number(size);
474 QPixmap pxIcon;
475
476 if (!QPixmapCache::find(kyIcon, &pxIcon)) {
477 pxIcon = Icons::get(ixIcon).pixmap(size); // Qt::AA_UseHighDpiPixmaps (in Qt 5.7) doesn't return highdpi pixmap
478 QPixmapCache::insert(kyIcon, pxIcon);
479 }
480
481 if (isClosed())
482 ixIcon = Icon::AccountClosed;
483 else if (reconcileFlag)
484 ixIcon = Icon::Reconciled;
485 else if (hasOnlineMapping())
486 ixIcon = Icon::Download;
487 else
488 return pxIcon;
489
490 QPixmap pxOverlay = Icons::get(ixIcon).pixmap(size);
491 QPainter pxPainter(&pxIcon);
492 const QSize szIcon = pxIcon.size();
493 pxPainter.drawPixmap(szIcon.width() / 2, szIcon.height() / 2,
494 szIcon.width() / 2, szIcon.height() / 2, pxOverlay);
495 return pxIcon;
496 }
497
accountTypeToString(const Account::Type accountType)498 QString MyMoneyAccount::accountTypeToString(const Account::Type accountType)
499 {
500 switch (accountType) {
501 case Account::Type::Checkings:
502 return i18nc("Account type", "Checking");
503 case Account::Type::Savings:
504 return i18nc("Account type", "Savings");
505 case Account::Type::CreditCard:
506 return i18nc("Account type", "Credit Card");
507 case Account::Type::Cash:
508 return i18nc("Account type", "Cash");
509 case Account::Type::Loan:
510 return i18nc("Account type", "Loan");
511 case Account::Type::CertificateDep:
512 return i18nc("Account type", "Certificate of Deposit");
513 case Account::Type::Investment:
514 return i18nc("Account type", "Investment");
515 case Account::Type::MoneyMarket:
516 return i18nc("Account type", "Money Market");
517 case Account::Type::Asset:
518 return i18nc("Account type", "Asset");
519 case Account::Type::Liability:
520 return i18nc("Account type", "Liability");
521 case Account::Type::Currency:
522 return i18nc("Account type", "Currency");
523 case Account::Type::Income:
524 return i18nc("Account type", "Income");
525 case Account::Type::Expense:
526 return i18nc("Account type", "Expense");
527 case Account::Type::AssetLoan:
528 return i18nc("Account type", "Investment Loan");
529 case Account::Type::Stock:
530 return i18nc("Account type", "Stock");
531 case Account::Type::Equity:
532 return i18nc("Account type", "Equity");
533 default:
534 return i18nc("Account type", "Unknown");
535 }
536 }
537
addReconciliation(const QDate & date,const MyMoneyMoney & amount)538 bool MyMoneyAccount::addReconciliation(const QDate& date, const MyMoneyMoney& amount)
539 {
540 Q_D(MyMoneyAccount);
541 d->m_reconciliationHistory[date] = amount;
542 QString history, sep;
543 QMap<QDate, MyMoneyMoney>::const_iterator it;
544 for (it = d->m_reconciliationHistory.constBegin();
545 it != d->m_reconciliationHistory.constEnd();
546 ++it) {
547
548 history += QString("%1%2:%3").arg(sep,
549 it.key().toString(Qt::ISODate),
550 (*it).toString());
551 sep = QLatin1Char(';');
552 }
553 setValue("reconciliationHistory", history);
554 return true;
555 }
556
reconciliationHistory()557 QMap<QDate, MyMoneyMoney> MyMoneyAccount::reconciliationHistory()
558 {
559 Q_D(MyMoneyAccount);
560 // check if the internal history member is already loaded
561 if (d->m_reconciliationHistory.count() == 0
562 && !value("reconciliationHistory").isEmpty()) {
563 QStringList entries = value("reconciliationHistory").split(';');
564 foreach (const QString& entry, entries) {
565 QStringList parts = entry.split(':');
566 QDate date = QDate::fromString(parts[0], Qt::ISODate);
567 MyMoneyMoney amount(parts[1]);
568 if (parts.count() == 2 && date.isValid()) {
569 d->m_reconciliationHistory[date] = amount;
570 }
571 }
572 }
573
574 return d->m_reconciliationHistory;
575 }
576
577 /**
578 * @todo Improve setting of country for nationalAccount
579 */
payeeIdentifiers() const580 QList< payeeIdentifier > MyMoneyAccount::payeeIdentifiers() const
581 {
582 QList< payeeIdentifier > list;
583
584 MyMoneyFile* file = MyMoneyFile::instance();
585
586 const auto strIBAN = QStringLiteral("iban");
587 const auto strBIC = QStringLiteral("bic");
588 // Iban & Bic
589 if (!value(strIBAN).isEmpty()) {
590 payeeIdentifierTyped<payeeIdentifiers::ibanBic> iban(new payeeIdentifiers::ibanBic);
591 iban->setIban(value(strIBAN));
592 iban->setBic(file->institution(institutionId()).value(strBIC));
593 iban->setOwnerName(file->user().name());
594 list.append(iban);
595 }
596
597 // National Account number
598 if (!number().isEmpty()) {
599 payeeIdentifierTyped<payeeIdentifiers::nationalAccount> national(new payeeIdentifiers::nationalAccount);
600 national->setAccountNumber(number());
601 national->setBankCode(file->institution(institutionId()).sortcode());
602 if (file->user().state().length() == 2)
603 national->setCountry(file->user().state());
604 national->setOwnerName(file->user().name());
605 list.append(national);
606 }
607
608 return list;
609 }
610
hasOnlineMapping() const611 bool MyMoneyAccount::hasOnlineMapping() const
612 {
613 Q_D(const MyMoneyAccount);
614 return !d->m_onlineBankingSettings.value(QLatin1String("provider")).isEmpty();
615 }
616
stdAccName(eMyMoney::Account::Standard stdAccID)617 QString MyMoneyAccount::stdAccName(eMyMoney::Account::Standard stdAccID)
618 {
619 static const QHash<eMyMoney::Account::Standard, QString> stdAccNames {
620 {eMyMoney::Account::Standard::Liability, QStringLiteral("AStd::Liability")},
621 {eMyMoney::Account::Standard::Asset, QStringLiteral("AStd::Asset")},
622 {eMyMoney::Account::Standard::Expense, QStringLiteral("AStd::Expense")},
623 {eMyMoney::Account::Standard::Income, QStringLiteral("AStd::Income")},
624 {eMyMoney::Account::Standard::Equity, QStringLiteral("AStd::Equity")},
625 };
626 return stdAccNames.value(stdAccID);
627 }
628